From 9ff904d0fa695433088801c96fd95f150b3bded6 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 31 May 2019 17:54:11 +1000 Subject: [PATCH] WIP: Add Endpoint Administration --- .../controllers/admin-wizards-api.js.es6 | 16 +++- .../discourse/models/custom-wizard-api.js.es6 | 31 ++++--- .../discourse/routes/admin-wizards-api.js.es6 | 2 +- .../discourse/templates/admin-wizards-api.hbs | 90 +++++++++++++------ assets/stylesheets/wizard_custom_admin.scss | 63 ++++++++++++- config/locales/client.en.yml | 18 +++- controllers/admin_api.rb | 55 ------------ controllers/api.rb | 69 ++++++++++++++ jobs/refresh_api_access_token.rb | 2 +- lib/api/api.rb | 16 ++++ lib/{ => api}/authorization.rb | 19 ++-- lib/api/endpoint.rb | 46 ++++++++++ plugin.rb | 25 +++--- serializers/api/api_serializer.rb | 19 ++++ .../authorization_serializer.rb} | 5 +- serializers/api/basic_api_serializer.rb | 3 + serializers/api/endpoint_serializer.rb | 5 ++ serializers/api_list_item_serializer.rb | 3 - 18 files changed, 356 insertions(+), 131 deletions(-) delete mode 100644 controllers/admin_api.rb create mode 100644 controllers/api.rb create mode 100644 lib/api/api.rb rename lib/{ => api}/authorization.rb (86%) create mode 100644 lib/api/endpoint.rb create mode 100644 serializers/api/api_serializer.rb rename serializers/{api_serializer.rb => api/authorization_serializer.rb} (76%) create mode 100644 serializers/api/basic_api_serializer.rb create mode 100644 serializers/api/endpoint_serializer.rb delete mode 100644 serializers/api_list_item_serializer.rb diff --git a/assets/javascripts/discourse/controllers/admin-wizards-api.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-api.js.es6 index 270dac4f..642eb9e7 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-api.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-api.js.es6 @@ -7,6 +7,7 @@ export default Ember.Controller.extend({ notAuthorized: Ember.computed.not('api.authorized'), authorizationTypes: ['oauth', 'basic'], isOauth: Ember.computed.equal('api.authType', 'oauth'), + endpointMethods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'], actions: { addParam() { @@ -17,6 +18,14 @@ export default Ember.Controller.extend({ this.get('api.authParams').removeObject(param); }, + addEndpoint() { + this.get('api.endpoints').pushObject({}); + }, + + removeEndpoint(endpoint) { + this.get('api.endpoints').removeObject(endpoint); + }, + authorize() { const api = this.get('api'); const { authType, authUrl, authParams } = api; @@ -62,9 +71,14 @@ export default Ember.Controller.extend({ data['password'] = api.get('password'); } + const endpoints = api.get('endpoints'); + if (endpoints.length) { + data['endpoints'] = JSON.stringify(endpoints); + } + this.set('savingApi', true); - ajax(`/admin/wizards/apis/${service}/save`, { + ajax(`/admin/wizards/apis/${service}`, { type: 'PUT', data }).catch(popupAjaxError) diff --git a/assets/javascripts/discourse/models/custom-wizard-api.js.es6 b/assets/javascripts/discourse/models/custom-wizard-api.js.es6 index 0bdec66a..6cfbae06 100644 --- a/assets/javascripts/discourse/models/custom-wizard-api.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-api.js.es6 @@ -12,21 +12,26 @@ const CustomWizardApi = Discourse.Model.extend({ CustomWizardApi.reopenClass({ create(params) { const api = this._super.apply(this); + const authorization = params.authorization; + const endpoints = params.endpoints; + api.setProperties({ service: params.service, - authType: params.auth_type, - authUrl: params.auth_url, - tokenUrl: params.token_url, - clientId: params.client_id, - clientSecret: params.client_secret, - authParams: Ember.A(params.auth_params), - authorized: params.authorized, - accessToken: params.access_token, - refreshToken: params.refresh_token, - code: params.code, - tokenExpiresAt: params.token_expires_at, - tokenRefreshAt: params.token_refresh_at + authType: authorization.auth_type, + authUrl: authorization.auth_url, + tokenUrl: authorization.token_url, + clientId: authorization.client_id, + clientSecret: authorization.client_secret, + authParams: Ember.A(authorization.auth_params), + authorized: authorization.authorized, + accessToken: authorization.access_token, + refreshToken: authorization.refresh_token, + code: authorization.code, + tokenExpiresAt: authorization.token_expires_at, + tokenRefreshAt: authorization.token_refresh_at, + endpoints: Ember.A(endpoints) }); + return api; }, @@ -34,7 +39,7 @@ CustomWizardApi.reopenClass({ return ajax(`/admin/wizards/apis/${service}`, { type: 'GET' }).then(result => { - return result; + return CustomWizardApi.create(result); }); }, diff --git a/assets/javascripts/discourse/routes/admin-wizards-api.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-api.js.es6 index 72f4ad2a..34320924 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-api.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-api.js.es6 @@ -10,6 +10,6 @@ export default Discourse.Route.extend({ }, setupController(controller, model){ - controller.set("api", CustomWizardApi.create(model)); + controller.set("api", model); } }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-api.hbs b/assets/javascripts/discourse/templates/admin-wizards-api.hbs index 9ab94f03..f91a4c3f 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-api.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-api.hbs @@ -1,11 +1,29 @@ -
+
+
+ {{input value=api.service placeholder=(i18n 'admin.wizard.api.service')}} +
+ +
+ {{#if savingApi}} + {{loading-spinner size="small"}} + {{/if}} + {{d-button label="admin.wizard.api.save" action="save" class="btn-primary"}} + {{d-button action="removeApi" label="admin.wizard.api.remove"}} +
+
+ +
+ {{i18n 'admin.wizard.api.auth'}} +
+ +
+
+ +
+ {{i18n 'admin.wizard.api.auth_settings'}} +
-
- -
- {{input value=api.service}} -
{{i18n 'admin.wizard.api.redirect_uri'}} {{api.redirectUri}} @@ -62,30 +80,26 @@
{{/if}} + +
+ {{d-button label="admin.wizard.api.authorize" action="authorize"}} +
-
- {{d-button label="admin.wizard.api.save" action="save"}} - {{#if savingApi}} - {{loading-spinner size="small"}} - {{/if}} -
+
+
+ {{i18n 'admin.wizard.api.auth_status'}} +
-
- {{d-button label="admin.wizard.api.authorize" action="authorize"}} -
- -
- {{#if api.authorized}} - - {{i18n "admin.wizard.api.authorized"}} - {{else}} - - {{i18n "admin.wizard.api.not_authorized"}} - {{/if}} -
- -
+
+ {{#if api.authorized}} + + {{i18n "admin.wizard.api.authorized"}} + {{else}} + + {{i18n "admin.wizard.api.not_authorized"}} + {{/if}} +
@@ -122,3 +136,25 @@
+ +
+ {{i18n 'admin.wizard.api.endpoint.label'}} +
+ +
+ {{d-button action='addEndpoint' label='admin.wizard.api.endpoint.add' icon='plus'}} + +
+
    + {{#each api.endpoints as |endpoint|}} +
  • +
    + {{combo-box content=endpointMethods value=endpoint.method none="admin.wizard.api.endpoint.method"}} + {{input value=endpoint.url placeholder=(i18n 'admin.wizard.api.endpoint.url') class='endpoint-url'}} + {{d-button action='removeEndpoint' actionParam=endpoint icon='times' class='remove-endpoint'}} +
    +
  • + {{/each}} +
+
+
diff --git a/assets/stylesheets/wizard_custom_admin.scss b/assets/stylesheets/wizard_custom_admin.scss index 10efb43a..7bec453f 100644 --- a/assets/stylesheets/wizard_custom_admin.scss +++ b/assets/stylesheets/wizard_custom_admin.scss @@ -298,6 +298,65 @@ } } -.wizard-step-contents{ - height: unset !important; +.wizard-step-contents { + height: unset !important; +} + +.admin-wizards-api { + margin-bottom: 40px; + + .content-list { + margin-right: 20px; + } +} + +.wizard-api-header { + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} + +.wizard-api-authentication { + display: flex; + background-color: $primary-very-low; + padding: 20px; + margin-bottom: 20px; + + .settings { + border-right: 1px solid #333; + margin-right: 10px; + padding-right: 20px; + } +} + +.wizard-api-endpoints { + background-color: $primary-very-low; + padding: 20px; + + .endpoint-list { + margin-top: 20px; + + ul { + margin: 0; + list-style: none; + } + } + + .endpoint { + display: flex; + + .combo-box { + width: 200px; + margin-right: 20px; + } + + .endpoint-url { + margin: 0; + width: 450px; + } + + .remove-endpoint { + margin-left: auto; + } + } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 33bfbc47..0df2b330 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -129,6 +129,10 @@ en: api: nav_label: 'APIs' new: 'New Api' + + auth: "Authentication" + auth_settings: "Settings" + auth_status: "Status" service: 'Api Service' redirect_uri: "Redirect Uri" auth_type: 'Authorization Type' @@ -137,8 +141,8 @@ en: auth_url: 'Authorization Url' client_id: 'Client Id' client_secret: 'Client Secret' - save: "Save" - authorize: 'Authorize' + + status: "Status" authorized: 'Authorized' not_authorized: "Not Authorized" params: 'Params' @@ -146,6 +150,16 @@ en: param_key: 'Param Key' param_value: 'Param Value' + remove: 'Delete' + authorize: 'Authorize' + save: "Save" + + endpoint: + label: "Endpoints" + add: "Add Endpoint" + method: "Select a method" + url: "Enter a url" + wizard_js: location: name: diff --git a/controllers/admin_api.rb b/controllers/admin_api.rb deleted file mode 100644 index 04f5a95f..00000000 --- a/controllers/admin_api.rb +++ /dev/null @@ -1,55 +0,0 @@ -class CustomWizard::AdminApiController < ::ApplicationController - before_action :ensure_logged_in - before_action :ensure_admin - skip_before_action :check_xhr, only: [:redirect] - - def index - end - - def list - serializer = ActiveModel::ArraySerializer.new( - CustomWizard::Authorization.list, - each_serializer: CustomWizard::ApiListItemSerializer - ) - - render json: MultiJson.dump(serializer) - end - - def find - params.require(:service) - render_serialized(CustomWizard::Authorization.get(params[:service]), CustomWizard::ApiSerializer, root: false) - end - - def save - params.require(:service) - - data = params.permit( - :service, - :auth_type, - :auth_url, - :token_url, - :client_id, - :client_secret, - :username, - :password, - :auth_params - ).to_h - - data[:auth_params] = JSON.parse(data[:auth_params]) if data[:auth_params] - - result = CustomWizard::Authorization.set(data[:service], data.except!(:service)) - - render json: success_json.merge(api: CustomWizard::ApiSerializer.new(result, root: false)) - end - - def redirect - params.require(:service) - params.require(:code) - - CustomWizard::Authorization.set(params[:service], code: params[:code]) - - CustomWizard::Authorization.get_token(params[:service]) - - return redirect_to path('/admin/wizards/apis/' + params[:service]) - end -end diff --git a/controllers/api.rb b/controllers/api.rb new file mode 100644 index 00000000..f16cba56 --- /dev/null +++ b/controllers/api.rb @@ -0,0 +1,69 @@ +class CustomWizard::ApiController < ::ApplicationController + before_action :ensure_logged_in + before_action :ensure_admin + skip_before_action :check_xhr, only: [:redirect] + + def index + end + + def list + serializer = ActiveModel::ArraySerializer.new( + CustomWizard::Api.list, + each_serializer: CustomWizard::BasicApiSerializer + ) + + render json: MultiJson.dump(serializer) + end + + def find + params.require(:service) + render_serialized(CustomWizard::Api.new(params[:service]), CustomWizard::ApiSerializer, root: false) + end + + def save + params.require(:service) + service = params.permit(:service) + + data[:auth_params] = JSON.parse(@auth_data[:auth_params]) if @auth_data[:auth_params] + + CustomWizard::Api::Authorization.set(service, @auth_data) + + @endpoint_data.each do |endpoint| + CustomWizard::Api::Endpoint.set(service, endpoint) + end + + render json: success_json.merge( + api: CustomWizard::ApiSerializer.new(params[:service], root: false) + ) + end + + def redirect + params.require(:service) + params.require(:code) + + CustomWizard::Api::Authorization.set(params[:service], code: params[:code]) + + CustomWizard::Api::Authorization.get_token(params[:service]) + + return redirect_to path('/admin/wizards/apis/' + params[:service]) + end + + private + + def auth_data + @auth_data ||= params.permit( + :auth_type, + :auth_url, + :token_url, + :client_id, + :client_secret, + :username, + :password, + :auth_params + ).to_h + end + + def endpoint_data + @endpoint_data ||= JSON.parse(params.permit(endpoints: [:id, :type, :url])) + end +end diff --git a/jobs/refresh_api_access_token.rb b/jobs/refresh_api_access_token.rb index 1b3221e8..c7fb94e5 100644 --- a/jobs/refresh_api_access_token.rb +++ b/jobs/refresh_api_access_token.rb @@ -1,7 +1,7 @@ module Jobs class RefreshApiAccessToken < Jobs::Base def execute(args) - CustomWizard::Authorization.refresh_token(args[:service]) + CustomWizard::Api::Authorization.refresh_token(args[:service]) end end end diff --git a/lib/api/api.rb b/lib/api/api.rb new file mode 100644 index 00000000..dde491d4 --- /dev/null +++ b/lib/api/api.rb @@ -0,0 +1,16 @@ +class CustomWizard::Api + include ActiveModel::SerializerSupport + + attr_accessor :service + + def initialize(service) + @service = service + end + + def self.list + PluginStoreRow.where("plugin_name LIKE 'custom_wizard_api_%' AND key = 'authorization'") + .map do |record| + self.new(record['plugin_name'].split('_').last) + end + end +end diff --git a/lib/authorization.rb b/lib/api/authorization.rb similarity index 86% rename from lib/authorization.rb rename to lib/api/authorization.rb index a9b27142..9ae5a211 100644 --- a/lib/authorization.rb +++ b/lib/api/authorization.rb @@ -1,10 +1,8 @@ require 'excon' -class CustomWizard::Authorization +class CustomWizard::Api::Authorization include ActiveModel::SerializerSupport - NGROK_URL = '' - attr_accessor :authorized, :service, :auth_type, @@ -41,21 +39,16 @@ class CustomWizard::Authorization model.send "#{k}=", v if model.respond_to?(k) end - PluginStore.set("custom_wizard_#{service}", 'authorization', model.as_json) + PluginStore.set("custom_wizard_api_#{service}", 'authorization', model.as_json) self.get(service) end def self.get(service) - data = PluginStore.get("custom_wizard_#{service}", 'authorization') + data = PluginStore.get("custom_wizard_api_#{service}", 'authorization') self.new(service, data) end - def self.list - PluginStoreRow.where("plugin_name LIKE 'custom_wizard_%' AND key = 'authorization'") - .map { |record| self.new(record['plugin_name'].split('_').last, record['value']) } - end - def self.get_header_authorization_string(service) protocol = authentication_protocol(service) raise Discourse::InvalidParameters.new(:service) unless protocol.present? @@ -77,7 +70,7 @@ class CustomWizard::Authorization end def self.get_token(service) - authorization = CustomWizard::Authorization.get(service) + authorization = CustomWizard::Api::Authorization.get(service) body = { client_id: authorization.client_id, @@ -99,7 +92,7 @@ class CustomWizard::Authorization end def self.refresh_token(service) - authorization = CustomWizard::Authorization.get(service) + authorization = CustomWizard::Api::Authorization.get(service) body = { grant_type: 'refresh_token', @@ -136,7 +129,7 @@ class CustomWizard::Authorization Jobs.enqueue_at(refresh_at, :refresh_api_access_token, opts) - CustomWizard::Authorization.set(service, + CustomWizard::Api::Authorization.set(service, access_token: access_token, refresh_token: refresh_token, token_expires_at: expires_at, diff --git a/lib/api/endpoint.rb b/lib/api/endpoint.rb new file mode 100644 index 00000000..b1e40e69 --- /dev/null +++ b/lib/api/endpoint.rb @@ -0,0 +1,46 @@ +class CustomWizard::Api::Endpoint + include ActiveModel::SerializerSupport + + attr_accessor :id, + :method, + :url + + def initialize(service, params) + @service = service + data = params.is_a?(String) ? ::JSON.parse(params) : params + + data.each do |k, v| + self.send "#{k}=", v if self.respond_to?(k) + end + end + + def self.set(service, data) + model = data[:endpoint_id] ? self.get(service, data[:endpoint_id]) : {} + endpoint_id = model[:endpoint_id] || SecureRandom.hex(8) + + data.each do |k, v| + model.send "#{k}=", v if model.respond_to?(k) + end + + PluginStore.set("custom_wizard_api_#{service}", "endpoint_#{endpoint_id}", model.as_json) + + self.get(service) + end + + def self.get(service, endpoint_id) + return nil if !endpoint_id + data = PluginStore.get("custom_wizard_api_#{service}", "endpoint_#{endpoint_id}") + data[:id] = endpoint_id + self.new(service, data) + end + + def self.list + PluginStoreRow.where("plugin_name LIKE 'custom_wizard_api_%' AND key LIKE 'endpoint_%'") + .map do |record| + service = record['plugin_name'].split('_').last + data = ::JSON.parse(record['value']) + data[:id] = record['key'].split('_').last + self.new(service, data) + end + end +end diff --git a/plugin.rb b/plugin.rb index 45725879..d35d929f 100644 --- a/plugin.rb +++ b/plugin.rb @@ -67,30 +67,35 @@ after_initialize do delete 'admin/wizards/custom/remove' => 'admin#remove' get 'admin/wizards/submissions' => 'admin#index' get 'admin/wizards/submissions/:wizard_id' => 'admin#submissions' - get 'admin/wizards/apis' => 'admin_api#list' - get 'admin/wizards/apis/new' => 'admin_api#index' - get 'admin/wizards/apis/:service' => 'admin_api#find' - put 'admin/wizards/apis/:service/save' => 'admin_api#save' - get 'admin/wizards/apis/:service/redirect' => 'admin_api#redirect' + get 'admin/wizards/apis' => 'api#list' + get 'admin/wizards/apis/new' => 'api#index' + get 'admin/wizards/apis/:service' => 'api#find' + put 'admin/wizards/apis/:service' => 'api#save' + get 'admin/wizards/apis/:service/redirect' => 'api#redirect' end end load File.expand_path('../jobs/clear_after_time_wizard.rb', __FILE__) load File.expand_path('../jobs/set_after_time_wizard.rb', __FILE__) - load File.expand_path('../jobs/refresh_api_access_token.rb', __FILE__) load File.expand_path('../lib/builder.rb', __FILE__) load File.expand_path('../lib/field.rb', __FILE__) load File.expand_path('../lib/step_updater.rb', __FILE__) load File.expand_path('../lib/template.rb', __FILE__) load File.expand_path('../lib/wizard.rb', __FILE__) load File.expand_path('../lib/wizard_edits.rb', __FILE__) - load File.expand_path('../lib/authorization.rb', __FILE__) load File.expand_path('../controllers/wizard.rb', __FILE__) load File.expand_path('../controllers/steps.rb', __FILE__) load File.expand_path('../controllers/admin.rb', __FILE__) - load File.expand_path('../controllers/admin_api.rb', __FILE__) - load File.expand_path('../serializers/api_serializer.rb', __FILE__) - load File.expand_path('../serializers/api_list_item_serializer.rb', __FILE__) + + load File.expand_path('../jobs/refresh_api_access_token.rb', __FILE__) + load File.expand_path('../lib/api/api.rb', __FILE__) + load File.expand_path('../lib/api/authorization.rb', __FILE__) + load File.expand_path('../lib/api/endpoint.rb', __FILE__) + load File.expand_path('../controllers/api.rb', __FILE__) + load File.expand_path('../serializers/api/api_serializer.rb', __FILE__) + load File.expand_path('../serializers/api/authorization_serializer.rb', __FILE__) + load File.expand_path('../serializers/api/basic_api_serializer.rb', __FILE__) + load File.expand_path('../serializers/api/endpoint_serializer.rb', __FILE__) ::UsersController.class_eval do def wizard_path diff --git a/serializers/api/api_serializer.rb b/serializers/api/api_serializer.rb new file mode 100644 index 00000000..aae825c2 --- /dev/null +++ b/serializers/api/api_serializer.rb @@ -0,0 +1,19 @@ +class CustomWizard::ApiSerializer < ApplicationSerializer + attributes :service, + :authorization, + :endpoints + + def authorization + CustomWizard::Api::AuthorizationSerializer.new( + CustomWizard::Api::Authorization.get(object.service), + root: false + ) + end + + def endpoints + ActiveModel::ArraySerializer.new( + CustomWizard::Api::Endpoint.list, + each_serializer: CustomWizard::Api::EndpointSerializer + ) + end +end diff --git a/serializers/api_serializer.rb b/serializers/api/authorization_serializer.rb similarity index 76% rename from serializers/api_serializer.rb rename to serializers/api/authorization_serializer.rb index 3bf86348..2ca347b5 100644 --- a/serializers/api_serializer.rb +++ b/serializers/api/authorization_serializer.rb @@ -1,6 +1,5 @@ -class CustomWizard::ApiSerializer < ApplicationSerializer - attributes :service, - :auth_type, +class CustomWizard::Api::AuthorizationSerializer < ApplicationSerializer + attributes :auth_type, :auth_url, :token_url, :client_id, diff --git a/serializers/api/basic_api_serializer.rb b/serializers/api/basic_api_serializer.rb new file mode 100644 index 00000000..b5430897 --- /dev/null +++ b/serializers/api/basic_api_serializer.rb @@ -0,0 +1,3 @@ +class CustomWizard::BasicApiSerializer < ApplicationSerializer + attributes :service +end diff --git a/serializers/api/endpoint_serializer.rb b/serializers/api/endpoint_serializer.rb new file mode 100644 index 00000000..7ca802c4 --- /dev/null +++ b/serializers/api/endpoint_serializer.rb @@ -0,0 +1,5 @@ +class CustomWizard::Api::EndpointSerializer < ApplicationSerializer + attributes :id, + :type, + :url +end diff --git a/serializers/api_list_item_serializer.rb b/serializers/api_list_item_serializer.rb deleted file mode 100644 index 854349c8..00000000 --- a/serializers/api_list_item_serializer.rb +++ /dev/null @@ -1,3 +0,0 @@ -class CustomWizard::ApiListItemSerializer < ApplicationSerializer - attributes :service -end