UX: Help users understand the meaning of each scope. (#10468)
This commit is contained in:
parent
882b0aac19
commit
390615fbcd
|
@ -3,6 +3,7 @@ import { isBlank } from "@ember/utils";
|
|||
import Controller from "@ember/controller";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default Controller.extend({
|
||||
userModes: [
|
||||
|
@ -48,6 +49,15 @@ export default Controller.extend({
|
|||
|
||||
continue() {
|
||||
this.transitionToRoute("adminApiKeys.show", this.model.id);
|
||||
},
|
||||
|
||||
showURLs(urls) {
|
||||
return showModal("admin-api-key-urls", {
|
||||
admin: true,
|
||||
model: {
|
||||
urls
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ import Controller from "@ember/controller";
|
|||
import { isEmpty } from "@ember/utils";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { empty } from "@ember/object/computed";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default Controller.extend(bufferedProperty("model"), {
|
||||
isNew: empty("model.id"),
|
||||
|
@ -51,6 +52,15 @@ export default Controller.extend(bufferedProperty("model"), {
|
|||
|
||||
undoRevokeKey(key) {
|
||||
key.undoRevoke().catch(popupAjaxError);
|
||||
},
|
||||
|
||||
showURLs(urls) {
|
||||
return showModal("admin-api-key-urls", {
|
||||
admin: true,
|
||||
model: {
|
||||
urls
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -37,12 +37,15 @@
|
|||
{{/admin-form-row}}
|
||||
|
||||
{{#unless useGlobalKey}}
|
||||
<div class="scopes-title">{{i18n "admin.api.scopes.title"}}</div>
|
||||
<p>{{i18n "admin.api.scopes.description"}}</p>
|
||||
{{#each-in scopes as |resource actions|}}
|
||||
<table class="scopes-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>{{resource}}</b></td>
|
||||
<td></td>
|
||||
<td>{{i18n "admin.api.scopes.allowed_urls"}}</td>
|
||||
<td>{{i18n "admin.api.scopes.optional_allowed_parameters"}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -50,7 +53,15 @@
|
|||
{{#each actions as |act|}}
|
||||
<tr>
|
||||
<td>{{input type="checkbox" checked=act.selected}}</td>
|
||||
<td><b>{{act.name}}</b></td>
|
||||
<td>
|
||||
<div class="scope-name">{{act.name}}</div>
|
||||
<span class="scope-tooltip" data-tooltip={{i18n (concat "admin.api.scopes.descriptions." resource "." act.key)}}>
|
||||
{{d-icon "question-circle"}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{d-button icon="link" action=(action "showURLs" act.urls) class="btn-info"}}
|
||||
</td>
|
||||
<td>
|
||||
{{#each act.params as |p|}}
|
||||
<div>
|
||||
|
|
|
@ -81,14 +81,14 @@
|
|||
{{/admin-form-row}}
|
||||
|
||||
{{#if model.api_key_scopes.length}}
|
||||
{{#admin-form-row label="admin.api.scopes.title"}}
|
||||
{{/admin-form-row}}
|
||||
<div class="scopes-title">{{i18n "admin.api.scopes.title"}}</div>
|
||||
|
||||
<table class="scopes-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{i18n "admin.api.scopes.resource"}}</td>
|
||||
<td>{{i18n "admin.api.scopes.action"}}</td>
|
||||
<td>{{i18n "admin.api.scopes.allowed_urls"}}</td>
|
||||
<td>{{i18n "admin.api.scopes.allowed_parameters"}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -96,7 +96,17 @@
|
|||
{{#each model.api_key_scopes as |scope|}}
|
||||
<tr>
|
||||
<td>{{scope.resource}}</td>
|
||||
<td>{{scope.action}}</td>
|
||||
<td>
|
||||
{{scope.action}}
|
||||
<span
|
||||
class="scope-tooltip"
|
||||
data-tooltip={{i18n (concat "admin.api.scopes.descriptions." scope.resource "." scope.key)}}>
|
||||
{{d-icon "question-circle"}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{d-button icon="link" action=(action "showURLs" scope.urls) class="btn-info"}}
|
||||
</td>
|
||||
<td>
|
||||
{{#each scope.parameters as |p|}}
|
||||
<div>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{{#d-modal-body title="admin.api.scopes.allowed_urls"}}
|
||||
<div>
|
||||
<ul>
|
||||
{{#each model.urls as |url|}}
|
||||
<li>
|
||||
<code>{{url}}</code>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/d-modal-body}}
|
|
@ -125,6 +125,20 @@ table.api-keys {
|
|||
text-align: left;
|
||||
width: 50%;
|
||||
}
|
||||
.scopes-title {
|
||||
font-size: $font-up-2;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.scope-name {
|
||||
font-weight: bold;
|
||||
font-size: $font-0;
|
||||
display: inline;
|
||||
}
|
||||
.scope-tooltip {
|
||||
font-size: $font-down-1;
|
||||
}
|
||||
}
|
||||
.scopes-table {
|
||||
margin: 20px 0 20px 0;
|
||||
|
|
|
@ -25,7 +25,15 @@ class Admin::ApiController < Admin::AdminController
|
|||
def scopes
|
||||
scopes = ApiKeyScope.scope_mappings.reduce({}) do |memo, (resource, actions)|
|
||||
memo.tap do |m|
|
||||
m[resource] = actions.map { |k, v| { id: "#{resource}:#{k}", name: k, params: v[:params] } }
|
||||
m[resource] = actions.map do |k, v|
|
||||
{
|
||||
id: "#{resource}:#{k}",
|
||||
key: k,
|
||||
name: k.to_s.gsub('_', ' '),
|
||||
params: v[:params],
|
||||
urls: v[:urls]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class ApiKeyScope < ActiveRecord::Base
|
|||
|
||||
class << self
|
||||
def list_actions
|
||||
actions = []
|
||||
actions = %w[list#category_feed]
|
||||
|
||||
TopTopic.periods.each do |p|
|
||||
actions.concat(["list#category_top_#{p}", "list#top_#{p}", "list#top_#{p}_feed"])
|
||||
|
@ -18,11 +18,20 @@ class ApiKeyScope < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def default_mappings
|
||||
{
|
||||
write_actions = %w[posts#create]
|
||||
read_actions = %w[topics#show topics#feed]
|
||||
|
||||
@default_mappings ||= {
|
||||
topics: {
|
||||
write: { actions: %w[posts#create topics#feed], params: %i[topic_id] },
|
||||
read: { actions: %w[topics#show], params: %i[topic_id], aliases: { topic_id: :id } },
|
||||
read_lists: { actions: list_actions, params: %i[category_id], aliases: { category_id: :category_slug_path_with_id } }
|
||||
write: { actions: write_actions, params: %i[topic_id], urls: find_urls(write_actions) },
|
||||
read: {
|
||||
actions: read_actions, params: %i[topic_id],
|
||||
aliases: { topic_id: :id }, urls: find_urls(read_actions)
|
||||
},
|
||||
read_lists: {
|
||||
actions: list_actions, params: %i[category_id],
|
||||
aliases: { category_id: :category_slug_path_with_id }, urls: find_urls(list_actions)
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -32,10 +41,26 @@ class ApiKeyScope < ActiveRecord::Base
|
|||
|
||||
default_mappings.tap do |mappings|
|
||||
plugin_mappings.each do |mapping|
|
||||
mapping[:urls] = find_urls(mapping[:actions])
|
||||
|
||||
mappings.deep_merge!(mapping)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_urls(actions)
|
||||
Rails.application.routes.routes.reduce([]) do |memo, route|
|
||||
defaults = route.defaults
|
||||
action = "#{defaults[:controller].to_s}##{defaults[:action]}"
|
||||
path = route.path.spec.to_s.gsub(/\(\.:format\)/, '')
|
||||
api_supported_path = path.end_with?('.rss') || route.path.requirements[:format]&.match?('json')
|
||||
excluded_paths = %w[/new-topic /new-message /exception]
|
||||
|
||||
memo.tap do |m|
|
||||
m << path if actions.include?(action) && api_supported_path && !excluded_paths.include?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def permits?(route_param)
|
||||
|
|
|
@ -5,9 +5,23 @@ class ApiKeyScopeSerializer < ApplicationSerializer
|
|||
attributes :resource,
|
||||
:action,
|
||||
:parameters,
|
||||
:allowed_parameters
|
||||
:urls,
|
||||
:allowed_parameters,
|
||||
:key
|
||||
|
||||
def parameters
|
||||
ApiKeyScope.scope_mappings.dig(object.resource.to_sym, object.action.to_sym, :params).to_a
|
||||
end
|
||||
|
||||
def urls
|
||||
ApiKeyScope.scope_mappings.dig(object.resource.to_sym, object.action.to_sym, :urls).to_a
|
||||
end
|
||||
|
||||
def action
|
||||
object.action.to_s.gsub('_', ' ')
|
||||
end
|
||||
|
||||
def key
|
||||
object.action
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3646,12 +3646,24 @@ en:
|
|||
continue: Continue
|
||||
use_global_key: Global Key (allows all actions)
|
||||
scopes:
|
||||
description: |
|
||||
When using scopes, you can restrict an API key to a specific set of endpoints.
|
||||
You can also define which parameters will be allowed. Use commas to separate multiple values.
|
||||
title: Scopes
|
||||
resource: Resource
|
||||
action: Action
|
||||
allowed_parameters: Allowed Parameters
|
||||
optional_allowed_parameters: Allowed Parameters (optional)
|
||||
any_parameter: (any parameter)
|
||||
allowed_urls: Allowed URLs
|
||||
descriptions:
|
||||
topics:
|
||||
read: |
|
||||
Read a topic or a specific post in it. RSS is also supported.
|
||||
write: |
|
||||
Create a new topic or post to an existing one.
|
||||
read_lists: |
|
||||
Read topic lists like top, new, latest, etc. RSS is also supported.
|
||||
|
||||
web_hooks:
|
||||
title: "Webhooks"
|
||||
|
|
Loading…
Reference in New Issue