From d558f2137534f7e8a1e32b0cfd2f6106b58fcd9b Mon Sep 17 00:00:00 2001 From: garrettmills Date: Sat, 16 May 2020 23:55:08 -0500 Subject: [PATCH] Implement OAuth2 server, link oauth:Client and auth::Oauth2Client, implement permission checks --- a.out | 0 app/assets/app/auth/MFADisable.component.js | 1 - app/assets/app/cobalt/Form.component.js | 194 +++++++---- app/assets/app/cobalt/Listing.component.js | 111 ++++--- app/assets/app/dash/NavBar.component.js | 7 + app/assets/app/dash/SideBar.component.js | 70 +++- .../app/dash/profile/EditProfile.component.js | 2 - .../profile/form/AppPassword.component.js | 1 - app/assets/app/resource/App.resource.js | 112 +++++++ app/assets/app/resource/CRUDBase.js | 18 +- .../app/resource/auth/Group.resource.js | 77 +++++ app/assets/app/resource/auth/Role.resource.js | 1 + app/assets/app/resource/auth/User.resource.js | 97 +++++- .../app/resource/iam/Policy.resource.js | 140 ++++++++ .../app/resource/ldap/Client.resource.js | 15 + .../app/resource/ldap/Group.resource.js | 1 + .../app/resource/oauth/Client.resource.js | 107 ++++++ .../app/resource/reflect/Scope.resource.js | 13 + .../app/resource/reflect/Token.resource.js | 89 +++++ .../app/resource/saml/Provider.resource.js | 13 + app/assets/app/service/Session.service.js | 6 + app/controllers/api/v1/App.controller.js | 272 +++++++++++++++ app/controllers/api/v1/Auth.controller.js | 314 +++++++++++++++++- app/controllers/api/v1/IAM.controller.js | 236 +++++++++++++ app/controllers/api/v1/OAuth.controller.js | 136 ++++++++ app/controllers/api/v1/Reflect.controller.js | 184 ++++++++++ app/controllers/saml/SAML.controller.js | 18 +- app/ldap/controllers/Groups.controller.js | 1 - app/models/Application.model.js | 29 ++ app/models/auth/Group.model.js | 35 ++ app/models/auth/User.model.js | 7 + app/models/iam/Policy.model.js | 127 +++++++ app/models/ldap/Client.model.js | 10 +- app/models/oauth/Client.model.js | 95 ++++++ app/models/saml/ServiceProvider.model.js | 9 + .../middleware/api/Permission.middleware.js | 10 + .../middleware/auth/APIRoute.middleware.js | 60 ++++ app/routing/routers/api/v1/app.routes.js | 41 +++ app/routing/routers/api/v1/auth.routes.js | 78 ++++- app/routing/routers/api/v1/iam.routes.js | 49 +++ app/routing/routers/api/v1/ldap.routes.js | 2 +- app/routing/routers/api/v1/message.routes.js | 12 +- app/routing/routers/api/v1/oauth.routes.js | 41 +++ app/routing/routers/api/v1/password.routes.js | 22 +- app/routing/routers/api/v1/profile.routes.js | 4 +- app/routing/routers/api/v1/reflect.routes.js | 50 +++ app/routing/routers/api/v1/saml.routes.js | 2 +- app/unit/LDAPServerUnit.js | 6 +- config/auth.config.js | 19 +- package.json | 3 +- yarn.lock | 20 +- 51 files changed, 2808 insertions(+), 159 deletions(-) create mode 100644 a.out create mode 100644 app/assets/app/resource/App.resource.js create mode 100644 app/assets/app/resource/auth/Group.resource.js create mode 100644 app/assets/app/resource/iam/Policy.resource.js create mode 100644 app/assets/app/resource/oauth/Client.resource.js create mode 100644 app/assets/app/resource/reflect/Scope.resource.js create mode 100644 app/assets/app/resource/reflect/Token.resource.js create mode 100644 app/controllers/api/v1/App.controller.js create mode 100644 app/controllers/api/v1/IAM.controller.js create mode 100644 app/controllers/api/v1/OAuth.controller.js create mode 100644 app/controllers/api/v1/Reflect.controller.js create mode 100644 app/models/Application.model.js create mode 100644 app/models/auth/Group.model.js create mode 100644 app/models/iam/Policy.model.js create mode 100644 app/models/oauth/Client.model.js create mode 100644 app/routing/middleware/auth/APIRoute.middleware.js create mode 100644 app/routing/routers/api/v1/app.routes.js create mode 100644 app/routing/routers/api/v1/iam.routes.js create mode 100644 app/routing/routers/api/v1/oauth.routes.js create mode 100644 app/routing/routers/api/v1/reflect.routes.js diff --git a/a.out b/a.out new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/app/auth/MFADisable.component.js b/app/assets/app/auth/MFADisable.component.js index 483e19a..6aadbb2 100644 --- a/app/assets/app/auth/MFADisable.component.js +++ b/app/assets/app/auth/MFADisable.component.js @@ -40,7 +40,6 @@ export default class MFADisableComponent extends Component { vue_on_create() { this.app_name = session.get('app.name') - console.log({session}) } async back_click() { diff --git a/app/assets/app/cobalt/Form.component.js b/app/assets/app/cobalt/Form.component.js index 1cee255..90caeb0 100644 --- a/app/assets/app/cobalt/Form.component.js +++ b/app/assets/app/cobalt/Form.component.js @@ -5,52 +5,111 @@ import { resource_service } from '../service/Resource.service.js' import { action_service } from '../service/Action.service.js' const template = ` -
-

- {{ title }} -

-
-
- - - - {{ field.error }} - - - - - {{ field.error }} - +
+ +
+
+
{{ access_msg }}
+
- -
- {{ error_message }} - {{ other_message }} - -
+
+ +

+ {{ title }} +

+
+
+

Loading...

+
+
+
+
+ + + + + {{ field.error }} + + + + + {{ field.error }} + + + + + {{ field.error }} + + + + + + {{ field.error }} + +
+
+
+ {{ error_message }} + {{ other_message }} + +
+
` @@ -80,6 +139,9 @@ export default class FormComponent extends Component { error_message = '' other_message = '' + access_msg = '' + can_access = false + is_ready = false mode = '' id = '' @@ -99,47 +161,60 @@ export default class FormComponent extends Component { this.mode = this.initial_mode this.id = this.form_id this.resource_class = await resource_service.get(this.resource) + + if ( await this.resource_class.can(this.mode) ) { + this.can_access = true + this.access_msg = true + } else { + this.can_access = false + this.access_msg = 'Sorry, you do not have permission to ' + this.mode + ' this resource.' + return + } + } else { this.reset() } this.uuid = utility.uuid() - await this.load() await this.init() + await this.load() } async init() { + this.definition = this.resource_class.form_definition for ( const field of this.definition.fields ) { if ( field.type.startsWith('select.dynamic') ) { field._options = field._options || field.options const rsc = await resource_service.get(field._options.resource) + const other_params = field._options.other_params || {} - field.options = (await rsc.list()).map(item => { + field.options = (await rsc.list(other_params)).map(item => { return { display: typeof field._options.display === 'function' ? field._options.display(item) : item[field._options.display || 'display'], value: typeof field._options.value === 'function' ? field._options.value(item) : item[field._options.value || 'display'], } }) } - - if ( field.type.endsWith('.multiple') ) { - this.data[field.field] = [] - } } - - this.is_ready = true - this.$nextTick(() => { - if ( this.mode !== 'view' ) this.$refs.input[0].focus() - }) } async load() { - this.definition = this.resource_class.form_definition if (this.mode !== 'insert') { this.data = await this.resource_class.get(this.id) } + for ( const field of this.definition.fields ) { + if ( field.type.endsWith('.multiple') && !this.data[field.field] ) { + this.data[field.field] = [] + } + } + this.title = title_map[this.mode] + ' ' + this.resource_class.item + + this.is_ready = true + this.$nextTick(() => { + if ( this.mode !== 'view' ) this.$refs.input[0].focus() + }) } async on_create() { @@ -171,9 +246,12 @@ export default class FormComponent extends Component { validate() { let valid = true for ( const field of this.definition.fields ) { - if ( field.required && (!(field.field in this.data) || !this.data[field.field]) ) { + if ( (Array.isArray(field.required) ? field.required.includes(this.mode) : field.required) && (!(field.field in this.data) || !this.data[field.field]) ) { field.error = 'This field is required.' valid = false + } else if ( field.type === 'password' && this.data[field.field] !== this.data[field.field + '-confirm'] ) { + field.error = field.name + ' confirmation does not match.' + valid = false } else { field.error = '' } diff --git a/app/assets/app/cobalt/Listing.component.js b/app/assets/app/cobalt/Listing.component.js index 460ebc9..5bcb203 100644 --- a/app/assets/app/cobalt/Listing.component.js +++ b/app/assets/app/cobalt/Listing.component.js @@ -1,49 +1,62 @@ import { Component } from '../../lib/vues6/vues6.js' import { action_service } from '../service/Action.service.js' import { message_service } from '../service/Message.service.js' +import { resource_service } from '../service/Resource.service.js' const template = `
-
-

{{ resource_class.plural }}

-
- + +
+
+

{{ access_msg }}

+
-
- - - - - - - - - - - - - - - -
#{{ col.name }}
{{ index + 1 }} - {{ col.renderer(row[col.field]) }} - {{ row[col.field] ? 'Yes' : 'No' }} - {{ col.field in row ? row[col.field] : '-' }} - - -
+ + +
+

{{ resource_class.plural }}

+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + +
#{{ col.name }}
{{ index + 1 }} + {{ col.renderer(row[col.field]) }} + {{ row[col.field] ? 'Yes' : 'No' }} + {{ col.field in row ? row[col.field] : '-' }} + + +
+
` @@ -56,17 +69,23 @@ export default class ListingComponent extends Component { data = [] resource_class = {} + access_msg = '' + can_access = false + async vue_on_create() { // Load the resource - const resource_mod = await import(`../resource/${this.resource}.resource.js`) - if ( !resource_mod ) - throw new Error('Unable to load Cobalt listing resource.') + this.resource_class = await resource_service.get(this.resource) - const rsc_name = this.resource.toLowerCase().replace(/\//g, '_') - if ( !resource_mod[rsc_name] ) - throw new Error('Unable to extract resource object from module.') + // Make sure we have permission + if ( !(await this.resource_class.can('list')) ) { + this.access_msg = 'Sorry, you do not have permission to view this resource.' + this.can_access = false + return + } else { + this.access_msg = '' + this.can_access = true + } - this.resource_class = resource_mod[rsc_name] await this.load() } diff --git a/app/assets/app/dash/NavBar.component.js b/app/assets/app/dash/NavBar.component.js index 2650db9..375e204 100644 --- a/app/assets/app/dash/NavBar.component.js +++ b/app/assets/app/dash/NavBar.component.js @@ -37,6 +37,7 @@ const template = ` > My Profile + API Tokens Sign-Out of {{ app_name }}
@@ -51,6 +52,8 @@ export default class NavBarComponent extends Component { static get template() { return template } static get props() { return [] } + can = {} + constructor() { super() this.toggle_event = event_bus.event('sidebar.toggle') @@ -59,6 +62,10 @@ export default class NavBarComponent extends Component { this.app_name = session.get('app.name') } + async vue_on_create() { + this.can.api_tokens = await session.check_permissions('v1:reflect:tokens:list') + } + toggle_sidebar() { this.toggle_event.fire() } diff --git a/app/assets/app/dash/SideBar.component.js b/app/assets/app/dash/SideBar.component.js index 374e876..1713e19 100644 --- a/app/assets/app/dash/SideBar.component.js +++ b/app/assets/app/dash/SideBar.component.js @@ -1,6 +1,8 @@ import { Component } from '../../lib/vues6/vues6.js' import { event_bus } from '../service/EventBus.service.js' import { action_service } from '../service/Action.service.js' +import { resource_service } from '../service/Resource.service.js' +import { session } from '../service/Session.service.js' const template = `