mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
* Move config options to SAML plugin
This moves all configuration options from Config.java to SAML auth manager. This
allows us to use the config framework.
* Make SAML2UserAuthenticator validate SAML token in httprequest
* Make logout API use ConfigKeys defined in saml auth manager
* Before doing SAML auth, cleanup local states and cookies
* Fix configurations in 4.5.1 to 4.5.2 upgrade path
* Fail if idp has no sso URL defined
* Add a default set of SAML SP cert for testing purposes
Now to enable and use saml, one needs to do a deploydb-saml after doing a deploydb
* UI remembers login selections, IDP server
- CLOUDSTACK-8458:
* On UI show dropdown list of discovered IdPs
* Support SAML Federation, where there may be more than one IdP
- New datastructure to hold metadata of SP or IdP
- Recursive processing of IdP metadata
- Fix login/logout APIs to get new interface and metadata data structure
- Add org/contact information to metadata
- Add new API: listIdps that returns list of all discovered IdPs
- Refactor and cleanup code and tests
- CLOUDSTACK-8459:
* Add HTTP-POST binding to SP metadata
* Authn requests must use either HTTP POST/Artifact binding
- CLOUDSTACK-8461:
* Use unspecified x509 cert as a fallback encryption/signing key
In case a IDP's metadata does not clearly say if their certificates need to be
used as signing or encryption and we don't find that, fallback to use the
unspecified key itself.
- CLOUDSTACK-8462:
* SAML Auth plugin should not do authorization
This removes logic to create user if they don't exist. This strictly now
assumes that users have been already created/imported/authorized by admins.
As per SAML v2.0 spec section 4.1.2, the SP provider should create authn requests using
either HTTP POST or HTTP Artifact binding to transfer the message through a
user agent (browser in our case). The use of HTTP Redirect was one of the reasons
why this plugin failed to work for some IdP servers that enforce this.
* Add new User Source
By reusing the source field, we can find if a user has been SAML enabled or not.
The limitation is that, once say a user is imported by LDAP and then SAML
enabled - they won't be able to use LDAP for authentication
* UI should allow users to pass in domain they want to log into, though it is
optional and needed only when a user has accounts across domains with same
username and authorized IDP server
* SAML users need to be authorized before they can authenticate
- New column entity to track saml entity id for a user
- Reusing source column to check if user is saml enabled or not
- Add new source types, saml2 and saml2disabled
- New table saml_token to solve the issue of multiple users across domains and
to enforce security by tracking authn token and checking the samlresponse for
the tokens
- Implement API: authorizeSamlSso to enable/disable saml authentication for a
user
- Stubs to implement saml token flushing/expiry
- CLOUDSTACK-8463:
* Use username attribute specified in global setting
Use username attribute defined by admin from a global setting
In case of encrypted assertion/attributes:
- Decrypt them
- Check signature if provided to check authenticity of message using IdP's
public key and SP's private key
- Loop through attributes to find the username
- CLOUDSTACK-8538:
* Add new global config for SAML request sig algorithm
- CLOUDSTACK-8539:
* Add metadata refresh timer task and token expiring
- Fix domain path and save it to saml_tokens
- Expire hour old saml tokens
- Refresh metadata based on timer task
- Fix unit tests
This closes #489
(cherry picked from commit 20ce346f3acb794b08a51841bab2188d426bf7dc)
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
Conflicts:
client/WEB-INF/classes/resources/messages_hu.properties
plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckHealthCommandWrapper.java
plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
ui/scripts/ui-custom/login.js
459 lines
22 KiB
JavaScript
459 lines
22 KiB
JavaScript
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
(function(cloudStack, $) {
|
|
$.extend(cloudStack, {
|
|
home: 'dashboard',
|
|
|
|
sectionPreFilter: function(args) {
|
|
var sections = [];
|
|
|
|
if (isAdmin()) {
|
|
sections = ["dashboard", "instances", "storage", "network", "templates", "accounts", "domains", "events", "system", "global-settings", "configuration", "projects", "regions", "affinityGroups"];
|
|
} else if (isDomainAdmin()) {
|
|
sections = ["dashboard", "instances", "storage", "network", "templates", "accounts", "domains", "events", "projects", "configuration", "regions", "affinityGroups"];
|
|
} else if (g_userProjectsEnabled) {
|
|
sections = ["dashboard", "instances", "storage", "network", "templates", "accounts", "events", "projects", "regions", "affinityGroups"];
|
|
} else { //normal user
|
|
sections = ["dashboard", "instances", "storage", "network", "templates", "accounts", "events", "regions", "affinityGroups"];
|
|
}
|
|
|
|
if (cloudStack.plugins.length) {
|
|
sections.push('plugins');
|
|
}
|
|
|
|
return sections;
|
|
},
|
|
sections: {
|
|
/**
|
|
* Dashboard
|
|
*/
|
|
dashboard: {},
|
|
instances: {},
|
|
affinityGroups: {},
|
|
storage: {},
|
|
network: {},
|
|
templates: {},
|
|
events: {},
|
|
projects: {},
|
|
accounts: {},
|
|
|
|
domains: {}, //domain-admin and root-admin only
|
|
|
|
regions: {}, //root-admin only
|
|
system: {}, //root-admin only
|
|
'global-settings': {}, //root-admin only
|
|
configuration: {}, //root-admin only
|
|
plugins: {}
|
|
}
|
|
});
|
|
|
|
$(window).bind('cloudStack.pluginReady', function() {
|
|
// Get language
|
|
g_lang = $.cookie('lang') ? $.cookie('lang') : 'en';
|
|
|
|
/**
|
|
* Generic error handling
|
|
*/
|
|
|
|
$.ajaxSetup({
|
|
url: clientApiUrl,
|
|
async: true,
|
|
dataType: 'json',
|
|
cache: false,
|
|
error: function(data) {
|
|
var clickAction = false;
|
|
if (isValidJsonString(data.responseText)) {
|
|
var json = JSON.parse(data.responseText);
|
|
if (json != null) {
|
|
var property;
|
|
for (property in json) {}
|
|
var errorObj = json[property];
|
|
if (errorObj.errorcode == 401 && errorObj.errortext == "unable to verify user credentials and/or request signature") {
|
|
clickAction = function() {
|
|
$('#user-options a').eq(0).trigger('click');
|
|
};
|
|
}
|
|
}
|
|
}
|
|
cloudStack.dialog.notice({
|
|
message: parseXMLHttpResponse(data),
|
|
clickAction: clickAction
|
|
});
|
|
}
|
|
});
|
|
|
|
var $container = $('#cloudStack3-container');
|
|
|
|
var loginArgs = {
|
|
$container: $container,
|
|
|
|
// Use this for checking the session, to bypass login screen
|
|
bypassLoginCheck: function(args) { //determine to show or bypass login screen
|
|
if (g_loginResponse == null) { //show login screen
|
|
/*
|
|
* Since we no longer store sessionKey in cookie, opening the
|
|
* 2nd browser window (of the same domain) will show login screen (i.e. user has to
|
|
* enter credentials again) and will cause the 1st browser window session timeout.
|
|
*/
|
|
var unBoxCookieValue = function (cookieName) {
|
|
var cookieValue = $.cookie(cookieName);
|
|
if (cookieValue && cookieValue.length > 2 && cookieValue[0] === '"' && cookieValue[cookieValue.length-1] === '"') {
|
|
cookieValue = cookieValue.slice(1, cookieValue.length-1);
|
|
$.cookie(cookieName, cookieValue, { expires: 1 });
|
|
}
|
|
return decodeURIComponent(cookieValue);
|
|
};
|
|
unBoxCookieValue('sessionkey');
|
|
// if sessionkey cookie exists use this to set g_sessionKey
|
|
// and destroy sessionkey cookie
|
|
if ($.cookie('sessionkey')) {
|
|
g_sessionKey = $.cookie('sessionkey');
|
|
$.cookie('sessionkey', null);
|
|
} else {
|
|
g_sessionKey = unBoxCookieValue('JSESSIONID');
|
|
}
|
|
g_role = unBoxCookieValue('role');
|
|
g_userid = unBoxCookieValue('userid');
|
|
g_domainid = unBoxCookieValue('domainid');
|
|
g_account = unBoxCookieValue('account');
|
|
g_username = unBoxCookieValue('username');
|
|
g_userfullname = unBoxCookieValue('userfullname');
|
|
g_timezone = unBoxCookieValue('timezone');
|
|
} else { //single-sign-on (bypass login screen)
|
|
g_sessionKey = encodeURIComponent(g_loginResponse.sessionkey);
|
|
g_role = g_loginResponse.type;
|
|
g_username = g_loginResponse.username;
|
|
g_userid = g_loginResponse.userid;
|
|
g_account = g_loginResponse.account;
|
|
g_domainid = g_loginResponse.domainid;
|
|
g_userfullname = g_loginResponse.firstname + ' ' + g_loginResponse.lastname;
|
|
g_timezone = g_loginResponse.timezone;
|
|
}
|
|
|
|
var userValid = false;
|
|
$.ajax({
|
|
url: createURL("listCapabilities"),
|
|
dataType: "json",
|
|
async: false,
|
|
success: function(json) {
|
|
g_capabilities = json.listcapabilitiesresponse.capability;
|
|
g_supportELB = json.listcapabilitiesresponse.capability.supportELB.toString(); //convert boolean to string if it's boolean
|
|
g_kvmsnapshotenabled = json.listcapabilitiesresponse.capability.kvmsnapshotenabled; //boolean
|
|
g_regionsecondaryenabled = json.listcapabilitiesresponse.capability.regionsecondaryenabled; //boolean
|
|
if (json.listcapabilitiesresponse.capability.userpublictemplateenabled != null) {
|
|
g_userPublicTemplateEnabled = json.listcapabilitiesresponse.capability.userpublictemplateenabled.toString(); //convert boolean to string if it's boolean
|
|
}
|
|
g_userProjectsEnabled = json.listcapabilitiesresponse.capability.allowusercreateprojects;
|
|
|
|
|
|
g_cloudstackversion = json.listcapabilitiesresponse.capability.cloudstackversion;
|
|
|
|
if (json.listcapabilitiesresponse.capability.apilimitinterval != null && json.listcapabilitiesresponse.capability.apilimitmax != null) {
|
|
var intervalLimit = ((json.listcapabilitiesresponse.capability.apilimitinterval * 1000) / json.listcapabilitiesresponse.capability.apilimitmax) * 3; //multiply 3 to be on safe side
|
|
//intervalLimit = 9999; //this line is for testing only, comment it before check in
|
|
if (intervalLimit > g_queryAsyncJobResultInterval)
|
|
g_queryAsyncJobResultInterval = intervalLimit;
|
|
}
|
|
|
|
userValid = true;
|
|
},
|
|
error: function(xmlHTTP) { //override default error handling, do nothing instead of showing error "unable to verify user credentials" on login screen
|
|
},
|
|
beforeSend: function(XMLHttpResponse) {
|
|
return true;
|
|
}
|
|
});
|
|
|
|
return userValid ? {
|
|
user: {
|
|
userid: g_userid,
|
|
username: g_username,
|
|
account: g_account,
|
|
name: g_userfullname,
|
|
role: g_role,
|
|
domainid: g_domainid
|
|
}
|
|
} : false;
|
|
|
|
return testAddUser;
|
|
},
|
|
|
|
// Actual login process, via form
|
|
loginAction: function(args) {
|
|
var array1 = [];
|
|
array1.push("&username=" + encodeURIComponent(args.data.username));
|
|
|
|
var password;
|
|
if (md5HashedLogin)
|
|
password = $.md5(args.data.password);
|
|
else
|
|
password = todb(args.data.password);
|
|
array1.push("&password=" + password);
|
|
|
|
var domain;
|
|
if (args.data.domain != null && args.data.domain.length > 0) {
|
|
if (args.data.domain.charAt(0) != "/")
|
|
domain = "/" + args.data.domain;
|
|
else
|
|
domain = args.data.domain;
|
|
array1.push("&domain=" + encodeURIComponent(domain));
|
|
} else {
|
|
array1.push("&domain=" + encodeURIComponent("/"));
|
|
}
|
|
|
|
var loginCmdText = array1.join("");
|
|
|
|
$.ajax({
|
|
type: "POST",
|
|
data: "command=login" + loginCmdText + "&response=json",
|
|
dataType: "json",
|
|
async: false,
|
|
success: function(json) {
|
|
var loginresponse = json.loginresponse;
|
|
|
|
g_sessionKey = encodeURIComponent(loginresponse.sessionkey);
|
|
g_role = loginresponse.type;
|
|
g_username = loginresponse.username;
|
|
g_userid = loginresponse.userid;
|
|
g_account = loginresponse.account;
|
|
g_domainid = loginresponse.domainid;
|
|
g_timezone = loginresponse.timezone;
|
|
g_userfullname = loginresponse.firstname + ' ' + loginresponse.lastname;
|
|
|
|
$.cookie('username', g_username, {
|
|
expires: 1
|
|
});
|
|
$.cookie('account', g_account, {
|
|
expires: 1
|
|
});
|
|
$.cookie('domainid', g_domainid, {
|
|
expires: 1
|
|
});
|
|
$.cookie('role', g_role, {
|
|
expires: 1
|
|
});
|
|
$.cookie('timezone', g_timezone, {
|
|
expires: 1
|
|
});
|
|
$.cookie('userfullname', g_userfullname, {
|
|
expires: 1
|
|
});
|
|
$.cookie('userid', g_userid, {
|
|
expires: 1
|
|
});
|
|
|
|
$.ajax({
|
|
url: createURL("listCapabilities"),
|
|
dataType: "json",
|
|
async: false,
|
|
success: function(json) {
|
|
g_capabilities = json.listcapabilitiesresponse.capability;
|
|
g_supportELB = json.listcapabilitiesresponse.capability.supportELB.toString(); //convert boolean to string if it's boolean
|
|
g_kvmsnapshotenabled = json.listcapabilitiesresponse.capability.kvmsnapshotenabled; //boolean
|
|
g_regionsecondaryenabled = json.listcapabilitiesresponse.capability.regionsecondaryenabled; //boolean
|
|
if (json.listcapabilitiesresponse.capability.userpublictemplateenabled != null) {
|
|
g_userPublicTemplateEnabled = json.listcapabilitiesresponse.capability.userpublictemplateenabled.toString(); //convert boolean to string if it's boolean
|
|
}
|
|
g_userProjectsEnabled = json.listcapabilitiesresponse.capability.allowusercreateprojects;
|
|
|
|
|
|
g_cloudstackversion = json.listcapabilitiesresponse.capability.cloudstackversion;
|
|
|
|
if (json.listcapabilitiesresponse.capability.apilimitinterval != null && json.listcapabilitiesresponse.capability.apilimitmax != null) {
|
|
var intervalLimit = ((json.listcapabilitiesresponse.capability.apilimitinterval * 1000) / json.listcapabilitiesresponse.capability.apilimitmax) * 3; //multiply 3 to be on safe side
|
|
//intervalLimit = 8888; //this line is for testing only, comment it before check in
|
|
if (intervalLimit > g_queryAsyncJobResultInterval)
|
|
g_queryAsyncJobResultInterval = intervalLimit;
|
|
}
|
|
|
|
args.response.success({
|
|
data: {
|
|
user: $.extend(true, {}, loginresponse, {
|
|
name: loginresponse.firstname + ' ' + loginresponse.lastname,
|
|
role: loginresponse.type == 1 ? 'admin' : 'user',
|
|
type: loginresponse.type
|
|
})
|
|
}
|
|
});
|
|
},
|
|
error: function(xmlHTTP) {
|
|
args.response.error();
|
|
}
|
|
});
|
|
|
|
// Get project configuration
|
|
// TEMPORARY -- replace w/ output of capability response, etc., once implemented
|
|
window.g_projectsInviteRequired = false;
|
|
},
|
|
error: function(XMLHttpRequest) {
|
|
var errorMsg = parseXMLHttpResponse(XMLHttpRequest);
|
|
if (errorMsg.length == 0 && XMLHttpRequest.status == 0)
|
|
errorMsg = dictionary['error.unable.to.reach.management.server'];
|
|
else
|
|
errorMsg = _l('error.invalid.username.password'); //override error message
|
|
args.response.error(errorMsg);
|
|
},
|
|
beforeSend: function(XMLHttpResponse) {
|
|
return true;
|
|
}
|
|
});
|
|
},
|
|
|
|
logoutAction: function(args) {
|
|
$.ajax({
|
|
url: createURL('logout'),
|
|
async: false,
|
|
success: function() {
|
|
g_sessionKey = null;
|
|
g_username = null;
|
|
g_account = null;
|
|
g_domainid = null;
|
|
g_timezoneoffset = null;
|
|
g_timezone = null;
|
|
g_supportELB = null;
|
|
g_kvmsnapshotenabled = null;
|
|
g_regionsecondaryenabled = null;
|
|
g_loginCmdText = null;
|
|
|
|
$.cookie('JSESSIONID', null);
|
|
$.cookie('sessionkey', null);
|
|
$.cookie('username', null);
|
|
$.cookie('account', null);
|
|
$.cookie('domainid', null);
|
|
$.cookie('role', null);
|
|
$.cookie('timezone', null);
|
|
|
|
if (onLogoutCallback()) { //onLogoutCallback() will set g_loginResponse(single-sign-on variable) to null, then bypassLoginCheck() will show login screen.
|
|
document.location.reload(); //when onLogoutCallback() returns true, reload the current document.
|
|
}
|
|
},
|
|
error: function() {
|
|
if (onLogoutCallback()) { //onLogoutCallback() will set g_loginResponse(single-sign-on variable) to null, then bypassLoginCheck() will show login screen.
|
|
document.location.reload(); //when onLogoutCallback() returns true, reload the current document.
|
|
}
|
|
},
|
|
beforeSend: function(XMLHttpResponse) {
|
|
return true;
|
|
}
|
|
});
|
|
},
|
|
|
|
samlLoginAction: function(args) {
|
|
g_sessionKey = null;
|
|
g_username = null;
|
|
g_account = null;
|
|
g_domainid = null;
|
|
g_timezoneoffset = null;
|
|
g_timezone = null;
|
|
g_supportELB = null;
|
|
g_kvmsnapshotenabled = null;
|
|
g_regionsecondaryenabled = null;
|
|
g_loginCmdText = null;
|
|
|
|
$.cookie('JSESSIONID', null);
|
|
$.cookie('sessionkey', null);
|
|
$.cookie('username', null);
|
|
$.cookie('account', null);
|
|
$.cookie('domainid', null);
|
|
$.cookie('role', null);
|
|
$.cookie('timezone', null);
|
|
var url = 'samlSso';
|
|
if (args.data.idpid) {
|
|
url = url + '&idpid=' + args.data.idpid;
|
|
}
|
|
if (args.data.domain) {
|
|
url = url + '&domain=' + args.data.domain;
|
|
}
|
|
window.location.href = createURL(url);
|
|
},
|
|
|
|
// Show cloudStack main UI widget
|
|
complete: function(args) {
|
|
var context = {
|
|
users: [args.user]
|
|
};
|
|
var cloudStackArgs = $.extend(cloudStack, {
|
|
context: context
|
|
});
|
|
|
|
// Check to invoke install wizard
|
|
cloudStack.installWizard.check({
|
|
context: context,
|
|
response: {
|
|
success: function(args) {
|
|
if (args.doInstall && isAdmin()) {
|
|
var initInstallWizard = function() {
|
|
cloudStack.uiCustom.installWizard({
|
|
$container: $container,
|
|
context: context,
|
|
complete: function() {
|
|
// Show cloudStack main UI
|
|
$container.cloudStack($.extend(cloudStackArgs, {
|
|
hasLogo: false
|
|
}));
|
|
}
|
|
});
|
|
};
|
|
|
|
initInstallWizard();
|
|
} else {
|
|
// Show cloudStack main UI
|
|
$container.cloudStack($.extend(cloudStackArgs, {
|
|
hasLogo: false
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Logout action
|
|
$('#user-options a').live('click', function() {
|
|
loginArgs.logoutAction({
|
|
context: cloudStack.context
|
|
});
|
|
});
|
|
|
|
window._reloadUI = function() {
|
|
$('#container').html('');
|
|
$('#container').cloudStack(window.cloudStack);
|
|
};
|
|
}
|
|
};
|
|
|
|
if ($.urlParam('loginUrl') != 0) {
|
|
// SSO
|
|
loginArgs.hideLoginScreen = true;
|
|
}
|
|
|
|
// Localization
|
|
if (!$.isFunction(cloudStack.localizationFn)) { // i.e., localize is overridden by a plugin/module
|
|
cloudStack.localizationFn = function(str) {
|
|
var localized = dictionary[str];
|
|
|
|
return localized ? localized : str;
|
|
};
|
|
}
|
|
|
|
// Localize validation messages
|
|
cloudStack.localizeValidatorMessages();
|
|
|
|
cloudStack.uiCustom.login(loginArgs);
|
|
|
|
document.title = _l('label.app.name');
|
|
});
|
|
})(cloudStack, jQuery);
|