Rohit Yadav 107595a6a5 CLOUDSTACK-8457: SAML auth plugin improvements for production usage
* 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
2015-06-29 12:31:51 +02:00

241 lines
8.8 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) {
/**
* Login process
*/
cloudStack.uiCustom.login = function(args) {
var $container = args.$container;
var $login = $('#template').find('.login').clone();
var $form = $login.find('form');
var $inputs = $form.find('input[type=text], input[type=password]');
var complete = args.complete;
var bypass = args.bypassLoginCheck && args.bypassLoginCheck();
// Check to see if we can bypass login screen
if (bypass) {
complete({
user: bypass.user
});
$(window).trigger('cloudStack.init');
return;
}
$login.appendTo('html body');
$('html body').addClass('login');
// Remove label if field was auto filled
$.each($form.find('label'), function() {
var $label = $(this);
var $input = $form.find('input').filter(function() {
return $(this).attr('name') == $label.attr('for');
});
if ($input.val()) {
$label.hide();
}
});
// Form validation
$form.validate();
// Form label behavior
$inputs.bind('keydown focus click blur', function(event) {
var $target = $(event.target);
var $label = $form.find('label').filter(function() {
return $(this).attr('for') == $target.attr('name');
});
if (event.type == 'keydown') {
$label.hide();
return true;
} else if (event.type == 'blur') {
if ($target.hasClass('first-input')) {
$target.removeClass('first-input');
}
if (!$(this).val()) {
$label.show();
}
} else {
if (!$target.hasClass('first-input')) {
$label.hide();
}
}
return true;
});
if (!args.hasLogo) $login.addClass('nologo');
// Labels cause related input to be focused
$login.find('label').click(function() {
var $input = $inputs.filter('[name=' + $(this).attr('for') + ']');
var $label = $(this);
$input.focus();
$label.hide();
});
$inputs.filter(':first').addClass('first-input').focus();
// Login action
var selectedLogin = 'cloudstack';
$login.find('#login-submit').click(function() {
if (selectedLogin === 'cloudstack') {
// CloudStack Local Login
if (!$form.valid()) return false;
var data = cloudStack.serializeForm($form);
args.loginAction({
data: data,
response: {
success: function(args) {
$login.remove();
$('html body').removeClass('login');
complete({
user: args.data.user
});
},
error: function(args) {
cloudStack.dialog.notice({
message: args
});
}
}
});
} else if (selectedLogin === 'saml') {
// SAML
args.samlLoginAction({
data: {'idpid': $login.find('#login-options').find(':selected').val(),
'domain': $login.find('#saml-domain').val()}
});
}
return false;
});
// Show SAML button if only SP is configured
$login.find('#login-dropdown').hide();
$login.find('#saml-login').hide();
$login.find('#cloudstack-login').hide();
var toggleLoginView = function (selectedOption) {
$login.find('#login-submit').show();
if (selectedOption === '') {
$login.find('#saml-login').hide();
$login.find('#cloudstack-login').hide();
$login.find('#login-submit').hide();
selectedLogin = 'none';
} else if (selectedOption === 'cloudstack-login') {
$login.find('#saml-login').hide();
$login.find('#cloudstack-login').show();
selectedLogin = 'cloudstack';
} else {
$login.find('#saml-login').show();
$login.find('#cloudstack-login').hide();
selectedLogin = 'saml';
}
};
$login.find('#login-options').change(function() {
var selectedOption = $login.find('#login-options').find(':selected').val();
toggleLoginView(selectedOption);
if (selectedOption && selectedOption !== '') {
$.cookie('login-option', selectedOption);
}
});
$.ajax({
type: 'GET',
url: createURL('listIdps'),
dataType: 'json',
async: false,
success: function(data, textStatus, xhr) {
if (xhr.status === 200) {
$login.find('#login-dropdown').show();
$login.find('#login-submit').hide();
} else {
$login.find('#cloudstack-login').show();
$login.find('#login-submit').show();
return;
}
$login.find('#login-options')
.append($('<option>', {
value: '',
text: '--- Select Identity Provider -- ',
selected: true
}));
if (data.listidpsresponse && data.listidpsresponse.idp) {
var idpList = data.listidpsresponse.idp.sort(function (a, b) {
return a.orgName.localeCompare(b.orgName);
});
g_idpList = idpList;
$.each(idpList, function(index, idp) {
$login.find('#login-options')
.append($('<option>', {
value: idp.id,
text: idp.orgName
}));
});
}
var loginOption = $.cookie('login-option');
if (loginOption) {
var option = $login.find('#login-options option[value="' + loginOption + '"]');
if (option.length > 0) {
option.prop('selected', true);
toggleLoginView(loginOption);
}
}
},
error: function(xhr) {
$login.find('#saml-login').hide();
$login.find('#cloudstack-login').show();
}
});
// Select language
var $languageSelect = $login.find('select[name=language]');
$languageSelect.change(function() {
if ($(this).val() != '') //language dropdown is not blank
$.cookie('lang', $(this).val()); //the selected option in language dropdown will be used (instead of browser's default language)
else //language dropdown is blank
$.cookie('lang', null); //null $.cookie('lang'), so browser's default language will be used.
document.location.reload();
});
$languageSelect.val($.cookie('lang'));
// Hide login screen, mainly for SSO
if (args.hideLoginScreen) {
$login.children().hide();
$login.append($('<div>').addClass('loading-overlay').append(
$('<span>').html(
// _l is not set yet, so localize directly to dictionary
// [should fix in future]
dictionary['label.loading'] + '...'
)
));
}
$(window).trigger('cloudStack.init');
};
})(jQuery, cloudStack);