From 74c9e76d09a3d7ad7bb71fb4ef7ff3fd3dd9b9c6 Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Thu, 16 Mar 2017 11:46:09 +0000 Subject: [PATCH] upgraded ember-data, basic keycloak callback mechanism --- app/.jshintrc | 3 +- app/app/authenticators/documize.js | 10 +-- app/app/authenticators/keycloak.js | 62 ++++++++++++++++ app/app/components/auth-settings.js | 39 ++++++++-- app/app/pods/auth/keycloak/controller.js | 14 ++++ app/app/pods/auth/keycloak/route.js | 64 ++++++++++++++++ app/app/pods/auth/keycloak/template.hbs | 4 + app/app/pods/auth/login/controller.js | 31 +++++++- app/app/pods/auth/login/route.js | 31 ++++++++ app/app/pods/auth/reset/route.js | 8 ++ app/app/pods/customize/auth/controller.js | 8 +- app/app/router.js | 3 + app/app/services/app-meta.js | 3 +- app/app/services/kc-auth.js | 74 +++++++++++++++++++ .../templates/components/auth-settings.hbs | 18 ++++- app/app/utils/constants.js | 9 ++- app/package.json | 2 +- 17 files changed, 352 insertions(+), 31 deletions(-) create mode 100644 app/app/authenticators/keycloak.js create mode 100644 app/app/pods/auth/keycloak/controller.js create mode 100644 app/app/pods/auth/keycloak/route.js create mode 100644 app/app/pods/auth/keycloak/template.hbs create mode 100644 app/app/services/kc-auth.js diff --git a/app/.jshintrc b/app/.jshintrc index f9da3157..3fc8e7c5 100644 --- a/app/.jshintrc +++ b/app/.jshintrc @@ -21,7 +21,8 @@ "Sortable", "datetimepicker", "Waypoint", - "velocity" + "velocity", + "Keycloak" ], "browser": true, "boss": true, diff --git a/app/app/authenticators/documize.js b/app/app/authenticators/documize.js index af522f9c..153d6a6b 100644 --- a/app/app/authenticators/documize.js +++ b/app/app/authenticators/documize.js @@ -21,7 +21,6 @@ const { } = Ember; export default Base.extend({ - ajax: service(), appMeta: service(), @@ -30,6 +29,7 @@ export default Base.extend({ if (data) { return resolve(data); } + return reject(); }, @@ -51,13 +51,9 @@ export default Base.extend({ return Ember.RSVP.reject("invalid"); } - var headers = { - 'Authorization': 'Basic ' + encoded - }; + let headers = { 'Authorization': 'Basic ' + encoded }; - return this.get('ajax').post('public/authenticate', { - headers - }); + return this.get('ajax').post('public/authenticate', { headers }); }, invalidate() { diff --git a/app/app/authenticators/keycloak.js b/app/app/authenticators/keycloak.js new file mode 100644 index 00000000..78197cc2 --- /dev/null +++ b/app/app/authenticators/keycloak.js @@ -0,0 +1,62 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import Ember from 'ember'; +import Base from 'ember-simple-auth/authenticators/base'; +import encodingUtil from '../utils/encoding'; +import netUtil from '../utils/net'; + +const { + isPresent, + RSVP: { resolve, reject }, + inject: { service } +} = Ember; + +export default Base.extend({ + ajax: service(), + appMeta: service(), + + restore(data) { + // TODO: verify authentication data + if (data) { + return resolve(data); + } + + return reject(); + }, + + authenticate(credentials) { + let domain = netUtil.getSubdomain(); + let encoded; + + if (typeof credentials === 'object') { + let { password, email } = credentials; + + if (!isPresent(password) || !isPresent(email)) { + return Ember.RSVP.reject("invalid"); + } + + encoded = encodingUtil.Base64.encode(`${domain}:${email}:${password}`); + } else if (typeof credentials === 'string') { + encoded = credentials; + } else { + return Ember.RSVP.reject("invalid"); + } + + let headers = { 'Authorization': 'Basic ' + encoded }; + + return this.get('ajax').post('public/authenticate', { headers }); + }, + + invalidate() { + return resolve(); + } +}); \ No newline at end of file diff --git a/app/app/components/auth-settings.js b/app/app/components/auth-settings.js index 66891618..3afff469 100644 --- a/app/app/components/auth-settings.js +++ b/app/app/components/auth-settings.js @@ -19,8 +19,15 @@ const { export default Ember.Component.extend({ isDocumizeProvider: computed.equal('authProvider', constants.AuthProvider.Documize), isKeycloakProvider: computed.equal('authProvider', constants.AuthProvider.Keycloak), - KeycloakConfigError: computed.empty('keycloakConfig'), - keycloakConfig: '', + + KeycloakUrlError: computed.empty('keycloakConfig.url'), + KeycloakRealmError: computed.empty('keycloakConfig.realm'), + KeycloakClientIdError: computed.empty('keycloakConfig.clientId'), + keycloakConfig: { + url: '', + realm: '', + clientId: '' + }, didReceiveAttrs() { this._super(...arguments); @@ -31,7 +38,15 @@ export default Ember.Component.extend({ case constants.AuthProvider.Documize: break; case constants.AuthProvider.Keycloak: - this.set('keycloakConfig', this.get('authConfig')); + let config = this.get('authConfig'); + + if (is.undefined(config) || is.null(config) || is.empty(config) ) { + config = {}; + } else { + config = JSON.parse(config); + } + + this.set('keycloakConfig', config); break; } }, @@ -46,11 +61,6 @@ export default Ember.Component.extend({ }, onSave() { - if (this.get('KeycloakConfigError')) { - this.$("#keycloak-id").focus(); - return; - } - let provider = this.get('authProvider'); let config = this.get('authConfig'); @@ -59,6 +69,19 @@ export default Ember.Component.extend({ config = {}; break; case constants.AuthProvider.Keycloak: + if (this.get('KeycloakUrlError')) { + this.$("#keycloak-url").focus(); + return; + } + if (this.get('KeycloakRealmError')) { + this.$("#keycloak-realm").focus(); + return; + } + if (this.get('KeycloakClientIdError')) { + this.$("#keycloak-clientId").focus(); + return; + } + config = this.get('keycloakConfig'); break; } diff --git a/app/app/pods/auth/keycloak/controller.js b/app/app/pods/auth/keycloak/controller.js new file mode 100644 index 00000000..17a79e72 --- /dev/null +++ b/app/app/pods/auth/keycloak/controller.js @@ -0,0 +1,14 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import Ember from 'ember'; + +export default Ember.Controller.extend({}); \ No newline at end of file diff --git a/app/app/pods/auth/keycloak/route.js b/app/app/pods/auth/keycloak/route.js new file mode 100644 index 00000000..c87a6b19 --- /dev/null +++ b/app/app/pods/auth/keycloak/route.js @@ -0,0 +1,64 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import Ember from 'ember'; +import constants from '../../../utils/constants'; + +export default Ember.Route.extend({ + session: Ember.inject.service(), + appMeta: Ember.inject.service(), + kcAuth: Ember.inject.service(), + queryParams: { + mode: { + refreshModel: false + } + }, + + beforeModel(transition) { + this.set('mode', is.not.undefined(transition.queryParams.mode) ? transition.queryParams.mode : 'login'); + + let authProvider = this.get('appMeta.authProvider'); + let authConfig = this.get('appMeta.authConfig'); + + if (authProvider !== constants.AuthProvider.Keycloak) { + console.log('Expecting keycloak auth but found ' + authProvider); + return; + } + + this.get('kcAuth').boot(JSON.parse(authConfig)).then((kc) => { + if (!kc.authenticated) { + this.get('kcAuth').login().then(() => { + }, (reject) => { + console.log(reject); + }); + } + + this.get('kcAuth').fetchProfile(kc).then((profile) => { + let data = this.get('kcAuth').mapProfile(kc, profile); + + console.log(data); + + // this.get("session").authenticate('authenticator:keycloak', data) + // .then(() => { + // this.transitionTo('folders'); + // }, () => { + // this.transitionTo('auth.login'); + // console.log(">>>>> Documize SSO failure"); + // }); + + }, (err) => { + console.log(err); + }); + }, (reason) => { + console.log(reason); + }); + }, +}); diff --git a/app/app/pods/auth/keycloak/template.hbs b/app/app/pods/auth/keycloak/template.hbs new file mode 100644 index 00000000..07400c1c --- /dev/null +++ b/app/app/pods/auth/keycloak/template.hbs @@ -0,0 +1,4 @@ +
+

Keycloak authentication...

+ +
diff --git a/app/app/pods/auth/login/controller.js b/app/app/pods/auth/login/controller.js index 8488ba34..24f68934 100644 --- a/app/app/pods/auth/login/controller.js +++ b/app/app/pods/auth/login/controller.js @@ -10,18 +10,18 @@ // https://documize.com import Ember from 'ember'; +// import constants from '../../../utils/constants'; export default Ember.Controller.extend({ - email: "", - password: "", + appMeta: Ember.inject.service('app-meta'), invalidCredentials: false, session: Ember.inject.service('session'), audit: Ember.inject.service('audit'), reset() { this.setProperties({ - email: "", - password: "" + email: '', + password: '' }); let dbhash = document.head.querySelector("[property=dbhash]").content; @@ -43,6 +43,29 @@ export default Ember.Controller.extend({ }).catch(() => { this.set('invalidCredentials', true); }); + + // let authProvider = this.get('appMeta.authProvider'); + // let authConfig = this.get('appMeta.authConfig'); + // switch (authProvider) { + // case constants.AuthProvider.Documize: + // let creds = this.getProperties('email', 'password'); + + // this.get('session').authenticate('authenticator:documize', creds) + // .then((response) => { + // this.get('audit').record("logged-in"); + // this.transitionToRoute('folders'); + // return response; + // }).catch(() => { + // this.set('invalidCredentials', true); + // }); + + // break; + + // case constants.AuthProvider.Keycloak: + // // this.get('session').authenticate('authenticator:keycloak', authConfig); + + // break; + // } } } }); diff --git a/app/app/pods/auth/login/route.js b/app/app/pods/auth/login/route.js index 289dd1bf..fe15df2e 100644 --- a/app/app/pods/auth/login/route.js +++ b/app/app/pods/auth/login/route.js @@ -10,11 +10,42 @@ // https://documize.com import Ember from 'ember'; +import constants from '../../../utils/constants'; export default Ember.Route.extend({ + appMeta: Ember.inject.service(), + kcAuth: Ember.inject.service(), + + beforeModel(/*transition*/) { + let authProvider = this.get('appMeta.authProvider'); + let authConfig = this.get('appMeta.authConfig'); + + switch (authProvider) { + case constants.AuthProvider.Keycloak: + this.get('kcAuth').boot(JSON.parse(authConfig)).then(() => { + this.get('kcAuth').login().then(() => { + }, (reject) => { + console.log(reject); + }); + }, (reject) => { + console.log(reject); + }); + + break; + } + }, + setupController: function (controller, model) { controller.set('model', model); controller.reset(); this.browser.setTitleAsPhrase("Login"); + }, + + activate() { + $('body').addClass('background-color-off-white'); + }, + + deactivate() { + $('body').removeClass('background-color-off-white'); } }); \ No newline at end of file diff --git a/app/app/pods/auth/reset/route.js b/app/app/pods/auth/reset/route.js index f20bcf9d..23433e24 100644 --- a/app/app/pods/auth/reset/route.js +++ b/app/app/pods/auth/reset/route.js @@ -15,4 +15,12 @@ export default Ember.Route.extend({ model: function (params) { return params.token; }, + + activate() { + $('body').addClass('background-color-off-white'); + }, + + deactivate() { + $('body').removeClass('background-color-off-white'); + } }); \ No newline at end of file diff --git a/app/app/pods/customize/auth/controller.js b/app/app/pods/customize/auth/controller.js index 41ecd7b7..6c965c4e 100644 --- a/app/app/pods/customize/auth/controller.js +++ b/app/app/pods/customize/auth/controller.js @@ -14,17 +14,17 @@ import NotifierMixin from "../../../mixins/notifier"; export default Ember.Controller.extend(NotifierMixin, { global: Ember.inject.service(), + appMeta: Ember.inject.service(), actions: { onSave(provider, config) { if(this.get('session.isGlobalAdmin')) { - let data = { authProvider: provider, authConfig: config }; + let data = { authProvider: provider, authConfig: JSON.stringify(config) }; return this.get('global').saveAuthConfig(data).then(() => { this.showNotification('Saved'); - if (this.get('authProvider') !== provider) { - // window.location.reload(); - } + this.set('appMeta.authProvider', provider); + this.set('appMeta.authConfig', config); }); } } diff --git a/app/app/router.js b/app/app/router.js index 7d8ab691..b6db841b 100644 --- a/app/app/router.js +++ b/app/app/router.js @@ -73,6 +73,9 @@ export default Router.map(function () { this.route('sso', { path: 'sso/:token' }); + this.route('keycloak', { + path: 'keycloak' + }); this.route('login', { path: 'login' }); diff --git a/app/app/services/app-meta.js b/app/app/services/app-meta.js index de07c566..9d70688c 100644 --- a/app/app/services/app-meta.js +++ b/app/app/services/app-meta.js @@ -11,6 +11,7 @@ import Ember from 'ember'; import config from '../config/environment'; +import constants from '../utils/constants'; const { String: { htmlSafe }, @@ -30,7 +31,7 @@ export default Ember.Service.extend({ edition: 'Community', valid: true, allowAnonymousAccess: false, - authProvider: 'documize', + authProvider: constants.AuthProvider.Documize, authConfig: null, setupMode: false, diff --git a/app/app/services/kc-auth.js b/app/app/services/kc-auth.js new file mode 100644 index 00000000..962dbd8d --- /dev/null +++ b/app/app/services/kc-auth.js @@ -0,0 +1,74 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import Ember from 'ember'; +import netUtil from '../utils/net'; + +const { + inject: { service } +} = Ember; + +export default Ember.Service.extend({ + sessionService: service('session'), + audit: service(), + ajax: service(), + appMeta: service(), + keycloak: null, + + boot(options) { + this.set('keycloak', new Keycloak(options)); + + return new Ember.RSVP.Promise((resolve, reject) => { + this.keycloak.init().success(() => { + this.get('audit').record("initialized-keycloak"); + resolve(this.get('keycloak')); + }).error((err) => { + console.log('Keycloak init failed', err); + reject(err); + }); + }); + }, + + login() { + let url = netUtil.getAppUrl(netUtil.getSubdomain()) + '/auth/keycloak?mode=login'; + + return new Ember.RSVP.Promise((resolve, reject) => { + if (this.get('keycloak').authenticated) { + return resolve(this.get('keycloak')); + } + + this.get('keycloak').login( {redirectUri: url} ); + return reject(); + }); + }, + + fetchProfile(kc) { + return new Ember.RSVP.Promise((resolve, reject) => { + kc.loadUserProfile().success((profile) => { + return resolve(profile); + }).error((err) => { + console.log('Keycloak loadUserProfile failed', err); + return reject(err); + }); + }); + }, + + mapProfile(kc, profile) { + return { + token: kc.token, + enabled: profile.enabled, + email: profile.email, + username: profile.username, + firstname: profile.firstName, + lastname: profile.lastName, + }; + } +}); diff --git a/app/app/templates/components/auth-settings.hbs b/app/app/templates/components/auth-settings.hbs index ee72e6ec..3f65bb93 100644 --- a/app/app/templates/components/auth-settings.hbs +++ b/app/app/templates/components/auth-settings.hbs @@ -7,14 +7,24 @@
External authentication servers, services must be accessible from the server running this Documize instance
{{#ui/ui-radio selected=isDocumizeProvider onClick=(action 'onDocumize')}}Documize — email/password{{/ui/ui-radio}} - {{#ui/ui-radio selected=isKeycloakProvider onClick=(action 'onKeycloak')}}Keycloak — bring your own authenticaiton server{{/ui/ui-radio}} + {{#ui/ui-radio selected=isKeycloakProvider onClick=(action 'onKeycloak')}}Keycloak — bring your own authentication server{{/ui/ui-radio}} {{#if isKeycloakProvider}}
- -
Realm Client JSON configuration from Keycloak admin console
- {{textarea id="keycloak-config" type="text" rows=7 value=keycloakConfig class=(if KeycloakUrlError 'error')}} + +
e.g. http://localhost:8888/auth
+ {{focus-input id="keycloak-url" type="text" value=keycloakConfig.url class=(if KeycloakUrlError 'error')}} +
+
+ +
e.g. main
+ {{input id="keycloak-realm" type="text" value=keycloakConfig.realm class=(if keycloakRealmError 'error')}} +
+
+ +
e.g. account
+ {{input id="keycloak-clientId" type="text" value=keycloakConfig.clientId class=(if KeycloakClientIdError 'error')}}
{{/if}} diff --git a/app/app/utils/constants.js b/app/app/utils/constants.js index 581b47d4..7af13a01 100644 --- a/app/app/utils/constants.js +++ b/app/app/utils/constants.js @@ -19,5 +19,12 @@ export default { AuthProvider: { Documize: 'documize', Keycloak: 'keycloak' - } + }, + + DocumentActionType: { + Read: 1, + Feedback: 2, + Contribute: 3, + Approve: 4 + } }; diff --git a/app/package.json b/app/package.json index 9e9d96ce..d3596bb9 100644 --- a/app/package.json +++ b/app/package.json @@ -37,7 +37,7 @@ "ember-cli-sri": "^2.1.0", "ember-cli-test-loader": "^1.1.0", "ember-cli-uglify": "^1.2.0", - "ember-data": "^2.11.0", + "ember-data": "^2.12.0", "ember-export-application-global": "^1.0.5", "ember-load-initializers": "^0.6.0", "ember-resolver": "^2.0.3",