summaryrefslogtreecommitdiffstats
path: root/install/ui
diff options
context:
space:
mode:
authorPetr Vobornik <pvoborni@redhat.com>2014-02-14 19:04:15 +0100
committerPetr Vobornik <pvoborni@redhat.com>2014-04-15 12:41:54 +0200
commit2ec5d969a27b91b04a2b424d93800e68a77aa6e8 (patch)
tree3612c17faded5dccf70d9c1d755b1fa96e34fa85 /install/ui
parent7c068f036f64b3b5156862fc2fc5855db612ef2e (diff)
downloadfreeipa-2ec5d969a27b91b04a2b424d93800e68a77aa6e8.tar.gz
freeipa-2ec5d969a27b91b04a2b424d93800e68a77aa6e8.tar.xz
freeipa-2ec5d969a27b91b04a2b424d93800e68a77aa6e8.zip
webui: authentication module
General purpose authentication interface and state. See doc of 'freeipa/auth' module. https://fedorahosted.org/freeipa/ticket/3903 Reviewed-By: Adam Misnyovszki <amisnyov@redhat.com>
Diffstat (limited to 'install/ui')
-rw-r--r--install/ui/doc/categories.json2
-rw-r--r--install/ui/src/freeipa/Application_controller.js58
-rw-r--r--install/ui/src/freeipa/auth.js252
-rw-r--r--install/ui/src/freeipa/ipa.js28
-rw-r--r--install/ui/src/freeipa/rpc.js31
5 files changed, 317 insertions, 54 deletions
diff --git a/install/ui/doc/categories.json b/install/ui/doc/categories.json
index 23792d364..17f7967be 100644
--- a/install/ui/doc/categories.json
+++ b/install/ui/doc/categories.json
@@ -18,6 +18,8 @@
"classes": [
"phases",
"_base.Phase_controller*",
+ "auth",
+ "auth.Auth",
"Application_controller",
"app",
"plugin_loader",
diff --git a/install/ui/src/freeipa/Application_controller.js b/install/ui/src/freeipa/Application_controller.js
index 135e9be87..c166e36ee 100644
--- a/install/ui/src/freeipa/Application_controller.js
+++ b/install/ui/src/freeipa/Application_controller.js
@@ -102,13 +102,13 @@ define([
on(this.app_widget, 'logout-click', lang.hitch(this, this.on_logout));
on(this.app_widget, 'password-reset-click', lang.hitch(this, this.on_password_reset));
on(this.app_widget, 'about-click', lang.hitch(this, this.on_about));
- on(this.menu, 'selected', lang.hitch(this, this.on_menu_select));
on(this.router, 'facet-show', lang.hitch(this, this.on_facet_show));
on(this.router, 'facet-change', lang.hitch(this, this.on_facet_change));
on(this.router, 'facet-change-canceled', lang.hitch(this, this.on_facet_canceled));
on(this.router, 'error', lang.hitch(this, this.on_router_error));
topic.subscribe('phase-error', lang.hitch(this, this.on_phase_error));
+ topic.subscribe('authenticate', lang.hitch(this, this.on_authenticate));
this.app_widget.render();
this.app_widget.hide();
@@ -263,6 +263,8 @@ define([
var new_facet = event.facet;
var current_facet = this.current_facet;
+ if (current_facet === new_facet) return;
+
if (current_facet && !current_facet.can_leave()) {
var permit_clb = lang.hitch(this, function() {
// Some facet's might not call reset before this call but after
@@ -417,29 +419,45 @@ define([
},
/**
- * Watches menu changes and adjusts facet space when there is
- * a need for larger menu space.
- *
- * Show extended menu space when:
- * * there is 3+ levels of menu
- *
- * Don't show when:
- * * all items of levels 3+ are hidden
+ * Starts authentication process in authentication UI
+ * @returns {undefined}
*/
- on_menu_select: function(select_state) {
+ on_authenticate: function() {
- var visible_levels = 0;
- var levels = select_state.new_selection.length;
- for (var i=0; i< levels; i++) {
- var item = select_state.new_selection[i];
- if(!item.hidden) visible_levels++;
- }
+ var self = this;
+ if (this.auth_ui === 'dialog') {
+ var dummy_command = {
+ execute: function() {
+ topic.publish('auth-successful');
+ }
+ };
- var three_levels = visible_levels >= 3;
+ var dialog = IPA.unauthorized_dialog({
+ close_on_escape: false,
+ error_thrown: { name: '', message: ''},
+ command: dummy_command
+ });
- dom_class.toggle(this.app_widget.content_node,
- 'nav-space-3',
- three_levels);
+ dialog.open();
+ } else {
+ var facet = this.current_facet;
+
+ // we don't want the load facet to be displayed after successful auth
+ if (facet && facet.name === 'load') {
+ facet = null;
+ }
+ var login_facet = reg.facet.get('login');
+
+ on.once(login_facet, "logged_in", function() {
+
+ if (facet) {
+ self.show_facet(facet);
+ }
+ topic.publish('auth-successful');
+ });
+
+ this.show_facet(login_facet);
+ }
}
});
diff --git a/install/ui/src/freeipa/auth.js b/install/ui/src/freeipa/auth.js
new file mode 100644
index 000000000..5e160a7a4
--- /dev/null
+++ b/install/ui/src/freeipa/auth.js
@@ -0,0 +1,252 @@
+/* Authors:
+ * Petr Vobornik <pvoborni@redhat.com>
+ *
+ * Copyright (C) 2014 Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+define([
+ 'dojo/_base/declare',
+ 'dojo/_base/lang',
+ 'dojo/Deferred',
+ 'dojo/Evented',
+ 'dojo/Stateful',
+ 'dojo/topic',
+ 'dojo/when'
+ ],
+ function(declare, lang, Deferred, Evented, Stateful, topic, when) {
+
+/**
+ * Authentication module
+ * @class auth
+ * @singleton
+ */
+var auth = {
+ /**
+ * Current authentication state
+ * @property {auth.Auth}
+ */
+ current: null
+};
+
+/**
+ * Authentication interface and state.
+ *
+ * Can be used for checking whether user is authenticated, by what method or
+ * what methods can be used for authentication. Actual authentication is
+ * done by separate object - authentication provider.
+ *
+ * Communication with authentication providers is done through global messages
+ * (`dojo/topic`).
+ *
+ * Some component can initiate the authentication process by calling:
+ *
+ * var auth_promise = auth.current.authenticate();
+ *
+ * `auth_promise` is a promise which is resolve on auth success and rejected
+ * on auth failure.
+ *
+ * Logout works in similar fashion:
+ *
+ * var logout_promise = auth.current.logout();
+ *
+ * The communication with authentication providers works as follows:
+ *
+ * 1. `auth.current.authenticate();` publishes `authenticate` topic
+ * 2. provider starts the authentication process
+ * 3. if it finishes with a success provider publishes `auth-successful`, if not
+ * it publishes `auth-failed`
+ * 4. the promise is resolved or rejected
+ *
+ * Logout works in similar fashion, only the topic names are `log-out`,
+ * `logout-successful` and `logout-failed`.
+ *
+ * New `authenticate` or `log-out` topics are not published if there is
+ * already authentication or logout in progress. The promises from subsequent
+ * `authenticate()` or `logout()` calls are resolved as expected.
+ *
+ * `login`, `principal`, `whoami`, `fullname` properties are supposed to be
+ * set by authentication providers.
+ *
+ * @class
+ */
+auth.Auth = declare([Stateful, Evented], {
+ /**
+ * Raw User information
+ *
+ * @property {Object}
+ */
+ whoami: {},
+
+ /**
+ * User is authenticated
+ *
+ * Use `set_authenticated(state, method)` for setting it.
+ *
+ * @property {boolean}
+ * @readonly
+ */
+ authenticated: false,
+
+ /**
+ * Method used for authentication
+ * @property {string}
+ */
+ authenticated_by: "",
+
+ /**
+ * Enabled auth methods
+ * @property {string[]}
+ */
+ auth_methods: ['kerberos', 'password'],
+
+ /**
+ * Authenticated user's Kerberos principal
+ * @property {string}
+ */
+ principal: "",
+
+ /**
+ * Authenticated user's login
+ * @property {string}
+ */
+ login: "",
+
+ /**
+ * Authenticated user's fullname
+ * @property {string}
+ */
+ fullname: "",
+
+ /**
+ * Authentication is in progress
+ * @property {boolean}
+ */
+ authenticating: false,
+
+ /**
+ * Logging out is in progress
+ * @property {boolean}
+ */
+ logging_out: false,
+
+ /**
+ * Indicates whether user was previously authenticated
+ * @property {boolean}
+ */
+ expired: false,
+
+ /**
+ * Update authenticated state
+ * @param {boolean} state User is authenticated
+ * @param {string} method used for authentication
+ */
+ set_authenticated: function(state, method) {
+
+ if (this.authenticated && !state) {
+ this.set('expired', true);
+ }
+
+ this.set('authenticated', state);
+ this.set('authenticated_by', method);
+
+ if (this.authenticated) {
+ this.set('expired', false);
+ }
+ },
+
+ /**
+ * Initiate authentication process (if not already initiated)
+ *
+ * Returns promise which is fulfilled when user is authenticated. It's
+ * rejected when authentication is canceled.
+ * @returns {Promise}
+ */
+ authenticate: function() {
+ var authenticated = new Deferred();
+ var ok_handler = topic.subscribe('auth-successful', function(info) {
+ authenticated.resolve(true);
+ ok_handler.remove();
+ fail_handler.remove();
+ });
+ var fail_handler = topic.subscribe('auth-failed', function(info) {
+ authenticated.reject();
+ ok_handler.remove();
+ fail_handler.remove();
+ });
+ if (!this.authenticating) {
+ topic.publish('authenticate', this);
+ }
+ return authenticated.promise;
+ },
+
+ /**
+ * Initiate logout process (if not already initiated)
+ *
+ * Returns promise which is fulfilled when user is logged-out. It's
+ * rejected when logout failed.
+ * @returns {Promise}
+ */
+ logout: function() {
+ var loggedout = new Deferred();
+ var ok_handler = topic.subscribe('logout-successful', function(info) {
+ loggedout.resolve(true);
+ ok_handler.remove();
+ fail_handler.remove();
+ });
+ var fail_handler = topic.subscribe('logout-failed', function(info) {
+ loggedout.reject();
+ ok_handler.remove();
+ fail_handler.remove();
+ });
+ if (!this.logging_out) {
+ topic.publish('log-out', this);
+ }
+ return loggedout.promise;
+ },
+
+ /**
+ * Initializes instance
+ *
+ * @private
+ */
+ postscript: function() {
+ var self = this;
+ var auth_true = function() {
+ self.set('authenticating', true);
+ };
+ var auth_false = function() {
+ self.set('authenticating', false);
+ };
+ var out_true = function() {
+ self.set('logging_out', true);
+ };
+ var out_false = function() {
+ self.set('logging_out', false);
+ };
+
+ topic.subscribe('auth-successful', auth_false);
+ topic.subscribe('auth-failed', auth_false);
+ topic.subscribe('authenticate', auth_true);
+ topic.subscribe('logout-successful', out_true);
+ topic.subscribe('logout-failed', out_true);
+ topic.subscribe('log-out', out_false);
+ }
+});
+
+auth.current = new auth.Auth();
+return auth;
+}); \ No newline at end of file
diff --git a/install/ui/src/freeipa/ipa.js b/install/ui/src/freeipa/ipa.js
index d6ae67d9c..a44d60b24 100644
--- a/install/ui/src/freeipa/ipa.js
+++ b/install/ui/src/freeipa/ipa.js
@@ -28,6 +28,7 @@ define([
'./jquery',
'./json2',
'./_base/i18n',
+ './auth',
'./datetime',
'./metadata',
'./builder',
@@ -35,7 +36,7 @@ define([
'./rpc',
'./text',
'exports'
- ], function(keys, topic, $, JSON, i18n, datetime, metadata_provider,
+ ], function(keys, topic, $, JSON, i18n, auth, datetime, metadata_provider,
builder, reg, rpc, text, exports) {
/**
@@ -107,9 +108,6 @@ var IPA = function () {
* - metadata
* - user information
* - server configuration
- * @property {boolean} logged_kerberos - User authenticated by
- * Kerberos negotiation
- * @property {boolean} logged_password - User authenticated by password
*/
that.ui = {};
@@ -362,7 +360,7 @@ IPA.object = function(s) {
/**
* Make request on Kerberos authentication url to initialize Kerberos negotiation.
*
- * Set result to IPA.ui.logged_kerberos.
+ * Set result to auth module.
*
* @member IPA
*/
@@ -371,12 +369,11 @@ IPA.get_credentials = function() {
function error_handler(xhr, text_status, error_thrown) {
status = xhr.status;
- IPA.ui.logged_kerberos = false;
}
function success_handler(data, text_status, xhr) {
status = xhr.status;
- IPA.ui.logged_kerberos = true;
+ auth.current.set_authenticated(true, 'kerberos');
}
var request = {
@@ -397,7 +394,7 @@ IPA.get_credentials = function() {
* Logout
*
* - terminate the session.
- * - redirect to logout landing page on success
+ * - reloads UI
*
* @member IPA
*/
@@ -412,21 +409,22 @@ IPA.logout = function() {
dialog.open();
}
- function redirect () {
- window.location = 'logout.html';
+ function reload () {
+ var l = window.location;
+ l.assign(l.href.split('#')[0]);
}
function success_handler(data, text_status, xhr) {
if (data && data.error) {
show_error(data.error.message);
} else {
- redirect();
+ reload();
}
}
function error_handler(xhr, text_status, error_thrown) {
if (xhr.status === 401) {
- redirect();
+ reload();
} else {
show_error(text_status);
}
@@ -461,7 +459,7 @@ IPA.login_password = function(username, password) {
function success_handler(data, text_status, xhr) {
result = 'success';
- IPA.ui.logged_password = true;
+ auth.current.set_authenticated(true, 'password');
}
function error_handler(xhr, text_status, error_thrown) {
@@ -475,8 +473,6 @@ IPA.login_password = function(username, password) {
result = reason;
}
}
-
- IPA.ui.logged_password = false;
}
var data = {
@@ -825,7 +821,7 @@ IPA.error_dialog = function(spec) {
IPA.confirm_mixin().apply(that);
/** @property {XMLHttpRequest} xhr Command's xhr */
- that.xhr = spec.xhr || {};
+ that.xhr = spec.xhr || null;
/** @property {string} text_status Command's text status */
that.text_status = spec.text_status || '';
/** @property {{name:string,message:string}} error_thrown Command's error */
diff --git a/install/ui/src/freeipa/rpc.js b/install/ui/src/freeipa/rpc.js
index bd5184650..6bb0ea228 100644
--- a/install/ui/src/freeipa/rpc.js
+++ b/install/ui/src/freeipa/rpc.js
@@ -23,12 +23,13 @@
*/
define([
- 'dojo/_base/lang',
+ 'dojo/_base/lang',
+ './auth',
'./ipa',
'./text',
'exports'
],
- function(lang, IPA, text, rpc /*exports*/) {
+ function(lang, auth, IPA, text, rpc /*exports*/) {
/**
* Call an IPA command over JSON-RPC.
@@ -206,19 +207,12 @@ rpc.command = function(spec) {
dialog.open();
}
- function auth_dialog_open(xhr, text_status, error_thrown) {
+ function error_handler_auth(xhr, text_status, error_thrown) {
- var ajax = this;
-
- var dialog = IPA.unauthorized_dialog({
- xhr: xhr,
- text_status: text_status,
- error_thrown: error_thrown,
- close_on_escape: false,
- command: that
+ auth.current.set_authenticated(false, '');
+ auth.current.authenticate().then(function() {
+ that.execute();
});
-
- dialog.open();
}
/*
@@ -259,7 +253,7 @@ rpc.command = function(spec) {
IPA.hide_activity_icon();
if (xhr.status === 401) {
- auth_dialog_open(xhr, text_status, error_thrown);
+ error_handler_auth(xhr, text_status, error_thrown);
return;
} else if (!error_thrown) {
error_thrown = {
@@ -281,13 +275,14 @@ rpc.command = function(spec) {
error_thrown.message = error_msg;
}
- // global specical cases error handlers section
+ // global special cases error handlers section
// With trusts, user from trusted domain can use his ticket but he
- // doesn't have rights for LDAP modify. It will throw internal errror.
+ // doesn't have rights for LDAP modify. It will throw internal error.
// We should offer form base login.
- if (xhr.status === 500 && IPA.ui.logged_kerberos && !IPA.ui.initialized) {
- auth_dialog_open(xhr, text_status, error_thrown);
+ if (xhr.status === 500 && auth.authenticated_by === 'kerberos' &&
+ !IPA.ui.initialized) {
+ error_handler_auth(xhr, text_status, error_thrown);
return;
}