mirror of
https://github.com/discourse/discourse.git
synced 2025-05-24 03:36:18 +08:00
UX: Help users understand the meaning of each scope. (#10468)
This commit is contained in:
@ -3,6 +3,7 @@ import { isBlank } from "@ember/utils";
|
|||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
userModes: [
|
userModes: [
|
||||||
@ -48,6 +49,15 @@ export default Controller.extend({
|
|||||||
|
|
||||||
continue() {
|
continue() {
|
||||||
this.transitionToRoute("adminApiKeys.show", this.model.id);
|
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 { isEmpty } from "@ember/utils";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { empty } from "@ember/object/computed";
|
import { empty } from "@ember/object/computed";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
export default Controller.extend(bufferedProperty("model"), {
|
export default Controller.extend(bufferedProperty("model"), {
|
||||||
isNew: empty("model.id"),
|
isNew: empty("model.id"),
|
||||||
@ -51,6 +52,15 @@ export default Controller.extend(bufferedProperty("model"), {
|
|||||||
|
|
||||||
undoRevokeKey(key) {
|
undoRevokeKey(key) {
|
||||||
key.undoRevoke().catch(popupAjaxError);
|
key.undoRevoke().catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
|
||||||
|
showURLs(urls) {
|
||||||
|
return showModal("admin-api-key-urls", {
|
||||||
|
admin: true,
|
||||||
|
model: {
|
||||||
|
urls
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -37,12 +37,15 @@
|
|||||||
{{/admin-form-row}}
|
{{/admin-form-row}}
|
||||||
|
|
||||||
{{#unless useGlobalKey}}
|
{{#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|}}
|
{{#each-in scopes as |resource actions|}}
|
||||||
<table class="scopes-table">
|
<table class="scopes-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>{{resource}}</b></td>
|
<td><b>{{resource}}</b></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
<td>{{i18n "admin.api.scopes.allowed_urls"}}</td>
|
||||||
<td>{{i18n "admin.api.scopes.optional_allowed_parameters"}}</td>
|
<td>{{i18n "admin.api.scopes.optional_allowed_parameters"}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -50,7 +53,15 @@
|
|||||||
{{#each actions as |act|}}
|
{{#each actions as |act|}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{input type="checkbox" checked=act.selected}}</td>
|
<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>
|
<td>
|
||||||
{{#each act.params as |p|}}
|
{{#each act.params as |p|}}
|
||||||
<div>
|
<div>
|
||||||
|
@ -81,14 +81,14 @@
|
|||||||
{{/admin-form-row}}
|
{{/admin-form-row}}
|
||||||
|
|
||||||
{{#if model.api_key_scopes.length}}
|
{{#if model.api_key_scopes.length}}
|
||||||
{{#admin-form-row label="admin.api.scopes.title"}}
|
<div class="scopes-title">{{i18n "admin.api.scopes.title"}}</div>
|
||||||
{{/admin-form-row}}
|
|
||||||
|
|
||||||
<table class="scopes-table">
|
<table class="scopes-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{i18n "admin.api.scopes.resource"}}</td>
|
<td>{{i18n "admin.api.scopes.resource"}}</td>
|
||||||
<td>{{i18n "admin.api.scopes.action"}}</td>
|
<td>{{i18n "admin.api.scopes.action"}}</td>
|
||||||
|
<td>{{i18n "admin.api.scopes.allowed_urls"}}</td>
|
||||||
<td>{{i18n "admin.api.scopes.allowed_parameters"}}</td>
|
<td>{{i18n "admin.api.scopes.allowed_parameters"}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -96,7 +96,17 @@
|
|||||||
{{#each model.api_key_scopes as |scope|}}
|
{{#each model.api_key_scopes as |scope|}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{scope.resource}}</td>
|
<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>
|
<td>
|
||||||
{{#each scope.parameters as |p|}}
|
{{#each scope.parameters as |p|}}
|
||||||
<div>
|
<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;
|
text-align: left;
|
||||||
width: 50%;
|
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 {
|
.scopes-table {
|
||||||
margin: 20px 0 20px 0;
|
margin: 20px 0 20px 0;
|
||||||
|
@ -25,7 +25,15 @@ class Admin::ApiController < Admin::AdminController
|
|||||||
def scopes
|
def scopes
|
||||||
scopes = ApiKeyScope.scope_mappings.reduce({}) do |memo, (resource, actions)|
|
scopes = ApiKeyScope.scope_mappings.reduce({}) do |memo, (resource, actions)|
|
||||||
memo.tap do |m|
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ class ApiKeyScope < ActiveRecord::Base
|
|||||||
|
|
||||||
class << self
|
class << self
|
||||||
def list_actions
|
def list_actions
|
||||||
actions = []
|
actions = %w[list#category_feed]
|
||||||
|
|
||||||
TopTopic.periods.each do |p|
|
TopTopic.periods.each do |p|
|
||||||
actions.concat(["list#category_top_#{p}", "list#top_#{p}", "list#top_#{p}_feed"])
|
actions.concat(["list#category_top_#{p}", "list#top_#{p}", "list#top_#{p}_feed"])
|
||||||
@ -18,11 +18,20 @@ class ApiKeyScope < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def default_mappings
|
def default_mappings
|
||||||
{
|
write_actions = %w[posts#create]
|
||||||
|
read_actions = %w[topics#show topics#feed]
|
||||||
|
|
||||||
|
@default_mappings ||= {
|
||||||
topics: {
|
topics: {
|
||||||
write: { actions: %w[posts#create topics#feed], params: %i[topic_id] },
|
write: { actions: write_actions, params: %i[topic_id], urls: find_urls(write_actions) },
|
||||||
read: { actions: %w[topics#show], params: %i[topic_id], aliases: { topic_id: :id } },
|
read: {
|
||||||
read_lists: { actions: list_actions, params: %i[category_id], aliases: { category_id: :category_slug_path_with_id } }
|
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
|
end
|
||||||
@ -32,10 +41,26 @@ class ApiKeyScope < ActiveRecord::Base
|
|||||||
|
|
||||||
default_mappings.tap do |mappings|
|
default_mappings.tap do |mappings|
|
||||||
plugin_mappings.each do |mapping|
|
plugin_mappings.each do |mapping|
|
||||||
|
mapping[:urls] = find_urls(mapping[:actions])
|
||||||
|
|
||||||
mappings.deep_merge!(mapping)
|
mappings.deep_merge!(mapping)
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def permits?(route_param)
|
def permits?(route_param)
|
||||||
|
@ -5,9 +5,23 @@ class ApiKeyScopeSerializer < ApplicationSerializer
|
|||||||
attributes :resource,
|
attributes :resource,
|
||||||
:action,
|
:action,
|
||||||
:parameters,
|
:parameters,
|
||||||
:allowed_parameters
|
:urls,
|
||||||
|
:allowed_parameters,
|
||||||
|
:key
|
||||||
|
|
||||||
def parameters
|
def parameters
|
||||||
ApiKeyScope.scope_mappings.dig(object.resource.to_sym, object.action.to_sym, :params).to_a
|
ApiKeyScope.scope_mappings.dig(object.resource.to_sym, object.action.to_sym, :params).to_a
|
||||||
end
|
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
|
end
|
||||||
|
@ -3646,12 +3646,24 @@ en:
|
|||||||
continue: Continue
|
continue: Continue
|
||||||
use_global_key: Global Key (allows all actions)
|
use_global_key: Global Key (allows all actions)
|
||||||
scopes:
|
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
|
title: Scopes
|
||||||
resource: Resource
|
resource: Resource
|
||||||
action: Action
|
action: Action
|
||||||
allowed_parameters: Allowed Parameters
|
allowed_parameters: Allowed Parameters
|
||||||
optional_allowed_parameters: Allowed Parameters (optional)
|
optional_allowed_parameters: Allowed Parameters (optional)
|
||||||
any_parameter: (any parameter)
|
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:
|
web_hooks:
|
||||||
title: "Webhooks"
|
title: "Webhooks"
|
||||||
|
Reference in New Issue
Block a user