1
0
Fork 0

WIP: Add Endpoint Administration

Dieser Commit ist enthalten in:
Angus McLeod 2019-05-31 17:54:11 +10:00
Ursprung 01a9e7f148
Commit 9ff904d0fa
18 geänderte Dateien mit 356 neuen und 131 gelöschten Zeilen

Datei anzeigen

@ -7,6 +7,7 @@ export default Ember.Controller.extend({
notAuthorized: Ember.computed.not('api.authorized'), notAuthorized: Ember.computed.not('api.authorized'),
authorizationTypes: ['oauth', 'basic'], authorizationTypes: ['oauth', 'basic'],
isOauth: Ember.computed.equal('api.authType', 'oauth'), isOauth: Ember.computed.equal('api.authType', 'oauth'),
endpointMethods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'],
actions: { actions: {
addParam() { addParam() {
@ -17,6 +18,14 @@ export default Ember.Controller.extend({
this.get('api.authParams').removeObject(param); this.get('api.authParams').removeObject(param);
}, },
addEndpoint() {
this.get('api.endpoints').pushObject({});
},
removeEndpoint(endpoint) {
this.get('api.endpoints').removeObject(endpoint);
},
authorize() { authorize() {
const api = this.get('api'); const api = this.get('api');
const { authType, authUrl, authParams } = api; const { authType, authUrl, authParams } = api;
@ -62,9 +71,14 @@ export default Ember.Controller.extend({
data['password'] = api.get('password'); data['password'] = api.get('password');
} }
const endpoints = api.get('endpoints');
if (endpoints.length) {
data['endpoints'] = JSON.stringify(endpoints);
}
this.set('savingApi', true); this.set('savingApi', true);
ajax(`/admin/wizards/apis/${service}/save`, { ajax(`/admin/wizards/apis/${service}`, {
type: 'PUT', type: 'PUT',
data data
}).catch(popupAjaxError) }).catch(popupAjaxError)

Datei anzeigen

@ -12,21 +12,26 @@ const CustomWizardApi = Discourse.Model.extend({
CustomWizardApi.reopenClass({ CustomWizardApi.reopenClass({
create(params) { create(params) {
const api = this._super.apply(this); const api = this._super.apply(this);
const authorization = params.authorization;
const endpoints = params.endpoints;
api.setProperties({ api.setProperties({
service: params.service, service: params.service,
authType: params.auth_type, authType: authorization.auth_type,
authUrl: params.auth_url, authUrl: authorization.auth_url,
tokenUrl: params.token_url, tokenUrl: authorization.token_url,
clientId: params.client_id, clientId: authorization.client_id,
clientSecret: params.client_secret, clientSecret: authorization.client_secret,
authParams: Ember.A(params.auth_params), authParams: Ember.A(authorization.auth_params),
authorized: params.authorized, authorized: authorization.authorized,
accessToken: params.access_token, accessToken: authorization.access_token,
refreshToken: params.refresh_token, refreshToken: authorization.refresh_token,
code: params.code, code: authorization.code,
tokenExpiresAt: params.token_expires_at, tokenExpiresAt: authorization.token_expires_at,
tokenRefreshAt: params.token_refresh_at tokenRefreshAt: authorization.token_refresh_at,
endpoints: Ember.A(endpoints)
}); });
return api; return api;
}, },
@ -34,7 +39,7 @@ CustomWizardApi.reopenClass({
return ajax(`/admin/wizards/apis/${service}`, { return ajax(`/admin/wizards/apis/${service}`, {
type: 'GET' type: 'GET'
}).then(result => { }).then(result => {
return result; return CustomWizardApi.create(result);
}); });
}, },

Datei anzeigen

@ -10,6 +10,6 @@ export default Discourse.Route.extend({
}, },
setupController(controller, model){ setupController(controller, model){
controller.set("api", CustomWizardApi.create(model)); controller.set("api", model);
} }
}); });

Datei anzeigen

@ -1,11 +1,29 @@
<div class="wizard-api-authorization"> <div class="wizard-api-header">
<div class="service">
<div class="details"> {{input value=api.service placeholder=(i18n 'admin.wizard.api.service')}}
<div class="control-group">
<label>{{i18n 'admin.wizard.api.service'}}</label>
<div class="controls">
{{input value=api.service}}
</div> </div>
<div class='buttons'>
{{#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"}}
</div>
</div>
<div class="wizard-header">
{{i18n 'admin.wizard.api.auth'}}
</div>
<div class="wizard-api-authentication">
<div class="settings">
<div class="wizard-header medium">
{{i18n 'admin.wizard.api.auth_settings'}}
</div>
<div class="control-group">
<div class="control-label"> <div class="control-label">
<span>{{i18n 'admin.wizard.api.redirect_uri'}}</span> <span>{{i18n 'admin.wizard.api.redirect_uri'}}</span>
<span>{{api.redirectUri}}</span> <span>{{api.redirectUri}}</span>
@ -62,18 +80,16 @@
</div> </div>
</div> </div>
{{/if}} {{/if}}
</div>
<div class='buttons'> <div class="actions">
{{d-button label="admin.wizard.api.save" action="save"}}
{{#if savingApi}}
{{loading-spinner size="small"}}
{{/if}}
</div>
<div class='buttons'>
{{d-button label="admin.wizard.api.authorize" action="authorize"}} {{d-button label="admin.wizard.api.authorize" action="authorize"}}
</div> </div>
</div>
<div class="status">
<div class="wizard-header medium">
{{i18n 'admin.wizard.api.auth_status'}}
</div>
<div class="authorization"> <div class="authorization">
{{#if api.authorized}} {{#if api.authorized}}
@ -84,8 +100,6 @@
<span>{{i18n "admin.wizard.api.not_authorized"}}</span> <span>{{i18n "admin.wizard.api.not_authorized"}}</span>
{{/if}} {{/if}}
</div> </div>
<div class="keys">
<div class="control-group"> <div class="control-group">
<label>Access Token:</label> <label>Access Token:</label>
<div class="controls"> <div class="controls">
@ -122,3 +136,25 @@
</div> </div>
</div> </div>
</div> </div>
<div class="wizard-header">
{{i18n 'admin.wizard.api.endpoint.label'}}
</div>
<div class="wizard-api-endpoints">
{{d-button action='addEndpoint' label='admin.wizard.api.endpoint.add' icon='plus'}}
<div class="endpoint-list">
<ul>
{{#each api.endpoints as |endpoint|}}
<li>
<div class="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'}}
</div>
</li>
{{/each}}
</ul>
</div>
</div>

Datei anzeigen

@ -298,6 +298,65 @@
} }
} }
.wizard-step-contents{ .wizard-step-contents {
height: unset !important; 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;
}
}
}

Datei anzeigen

@ -129,6 +129,10 @@ en:
api: api:
nav_label: 'APIs' nav_label: 'APIs'
new: 'New Api' new: 'New Api'
auth: "Authentication"
auth_settings: "Settings"
auth_status: "Status"
service: 'Api Service' service: 'Api Service'
redirect_uri: "Redirect Uri" redirect_uri: "Redirect Uri"
auth_type: 'Authorization Type' auth_type: 'Authorization Type'
@ -137,8 +141,8 @@ en:
auth_url: 'Authorization Url' auth_url: 'Authorization Url'
client_id: 'Client Id' client_id: 'Client Id'
client_secret: 'Client Secret' client_secret: 'Client Secret'
save: "Save"
authorize: 'Authorize' status: "Status"
authorized: 'Authorized' authorized: 'Authorized'
not_authorized: "Not Authorized" not_authorized: "Not Authorized"
params: 'Params' params: 'Params'
@ -146,6 +150,16 @@ en:
param_key: 'Param Key' param_key: 'Param Key'
param_value: 'Param Value' 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: wizard_js:
location: location:
name: name:

Datei anzeigen

@ -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

69
controllers/api.rb Normale Datei
Datei anzeigen

@ -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

Datei anzeigen

@ -1,7 +1,7 @@
module Jobs module Jobs
class RefreshApiAccessToken < Jobs::Base class RefreshApiAccessToken < Jobs::Base
def execute(args) def execute(args)
CustomWizard::Authorization.refresh_token(args[:service]) CustomWizard::Api::Authorization.refresh_token(args[:service])
end end
end end
end end

16
lib/api/api.rb Normale Datei
Datei anzeigen

@ -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

Datei anzeigen

@ -1,10 +1,8 @@
require 'excon' require 'excon'
class CustomWizard::Authorization class CustomWizard::Api::Authorization
include ActiveModel::SerializerSupport include ActiveModel::SerializerSupport
NGROK_URL = ''
attr_accessor :authorized, attr_accessor :authorized,
:service, :service,
:auth_type, :auth_type,
@ -41,21 +39,16 @@ class CustomWizard::Authorization
model.send "#{k}=", v if model.respond_to?(k) model.send "#{k}=", v if model.respond_to?(k)
end end
PluginStore.set("custom_wizard_#{service}", 'authorization', model.as_json) PluginStore.set("custom_wizard_api_#{service}", 'authorization', model.as_json)
self.get(service) self.get(service)
end end
def self.get(service) def self.get(service)
data = PluginStore.get("custom_wizard_#{service}", 'authorization') data = PluginStore.get("custom_wizard_api_#{service}", 'authorization')
self.new(service, data) self.new(service, data)
end 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) def self.get_header_authorization_string(service)
protocol = authentication_protocol(service) protocol = authentication_protocol(service)
raise Discourse::InvalidParameters.new(:service) unless protocol.present? raise Discourse::InvalidParameters.new(:service) unless protocol.present?
@ -77,7 +70,7 @@ class CustomWizard::Authorization
end end
def self.get_token(service) def self.get_token(service)
authorization = CustomWizard::Authorization.get(service) authorization = CustomWizard::Api::Authorization.get(service)
body = { body = {
client_id: authorization.client_id, client_id: authorization.client_id,
@ -99,7 +92,7 @@ class CustomWizard::Authorization
end end
def self.refresh_token(service) def self.refresh_token(service)
authorization = CustomWizard::Authorization.get(service) authorization = CustomWizard::Api::Authorization.get(service)
body = { body = {
grant_type: 'refresh_token', grant_type: 'refresh_token',
@ -136,7 +129,7 @@ class CustomWizard::Authorization
Jobs.enqueue_at(refresh_at, :refresh_api_access_token, opts) Jobs.enqueue_at(refresh_at, :refresh_api_access_token, opts)
CustomWizard::Authorization.set(service, CustomWizard::Api::Authorization.set(service,
access_token: access_token, access_token: access_token,
refresh_token: refresh_token, refresh_token: refresh_token,
token_expires_at: expires_at, token_expires_at: expires_at,

46
lib/api/endpoint.rb Normale Datei
Datei anzeigen

@ -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

Datei anzeigen

@ -67,30 +67,35 @@ after_initialize do
delete 'admin/wizards/custom/remove' => 'admin#remove' delete 'admin/wizards/custom/remove' => 'admin#remove'
get 'admin/wizards/submissions' => 'admin#index' get 'admin/wizards/submissions' => 'admin#index'
get 'admin/wizards/submissions/:wizard_id' => 'admin#submissions' get 'admin/wizards/submissions/:wizard_id' => 'admin#submissions'
get 'admin/wizards/apis' => 'admin_api#list' get 'admin/wizards/apis' => 'api#list'
get 'admin/wizards/apis/new' => 'admin_api#index' get 'admin/wizards/apis/new' => 'api#index'
get 'admin/wizards/apis/:service' => 'admin_api#find' get 'admin/wizards/apis/:service' => 'api#find'
put 'admin/wizards/apis/:service/save' => 'admin_api#save' put 'admin/wizards/apis/:service' => 'api#save'
get 'admin/wizards/apis/:service/redirect' => 'admin_api#redirect' get 'admin/wizards/apis/:service/redirect' => 'api#redirect'
end end
end end
load File.expand_path('../jobs/clear_after_time_wizard.rb', __FILE__) 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/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/builder.rb', __FILE__)
load File.expand_path('../lib/field.rb', __FILE__) load File.expand_path('../lib/field.rb', __FILE__)
load File.expand_path('../lib/step_updater.rb', __FILE__) load File.expand_path('../lib/step_updater.rb', __FILE__)
load File.expand_path('../lib/template.rb', __FILE__) load File.expand_path('../lib/template.rb', __FILE__)
load File.expand_path('../lib/wizard.rb', __FILE__) load File.expand_path('../lib/wizard.rb', __FILE__)
load File.expand_path('../lib/wizard_edits.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/wizard.rb', __FILE__)
load File.expand_path('../controllers/steps.rb', __FILE__) load File.expand_path('../controllers/steps.rb', __FILE__)
load File.expand_path('../controllers/admin.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('../jobs/refresh_api_access_token.rb', __FILE__)
load File.expand_path('../serializers/api_list_item_serializer.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 ::UsersController.class_eval do
def wizard_path def wizard_path

Datei anzeigen

@ -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

Datei anzeigen

@ -1,6 +1,5 @@
class CustomWizard::ApiSerializer < ApplicationSerializer class CustomWizard::Api::AuthorizationSerializer < ApplicationSerializer
attributes :service, attributes :auth_type,
:auth_type,
:auth_url, :auth_url,
:token_url, :token_url,
:client_id, :client_id,

Datei anzeigen

@ -0,0 +1,3 @@
class CustomWizard::BasicApiSerializer < ApplicationSerializer
attributes :service
end

Datei anzeigen

@ -0,0 +1,5 @@
class CustomWizard::Api::EndpointSerializer < ApplicationSerializer
attributes :id,
:type,
:url
end

Datei anzeigen

@ -1,3 +0,0 @@
class CustomWizard::ApiListItemSerializer < ApplicationSerializer
attributes :service
end