mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
"classes": [
|
||||
"phases",
|
||||
"_base.Phase_controller*",
|
||||
"auth",
|
||||
"auth.Auth",
|
||||
"Application_controller",
|
||||
"app",
|
||||
"plugin_loader",
|
||||
|
||||
@@ -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 dialog = IPA.unauthorized_dialog({
|
||||
close_on_escape: false,
|
||||
error_thrown: { name: '', message: ''},
|
||||
command: dummy_command
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
var three_levels = visible_levels >= 3;
|
||||
|
||||
dom_class.toggle(this.app_widget.content_node,
|
||||
'nav-space-3',
|
||||
three_levels);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
252
install/ui/src/freeipa/auth.js
Normal file
252
install/ui/src/freeipa/auth.js
Normal file
@@ -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;
|
||||
});
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user