diff -uNr one/install.sh one_patched/install.sh --- one/install.sh 2013-06-14 12:22:30.655005644 +0200 +++ one_patched/install.sh 2013-06-14 14:33:17.485704834 +0200 @@ -312,6 +312,7 @@ $SUNSTONE_LOCATION/public/vendor/4.0/jdpicker_1.1 \ $SUNSTONE_LOCATION/public/vendor/4.0/jdpicker_1.1/images \ $SUNSTONE_LOCATION/public/vendor/4.0/datetimepicker \ + $SUNSTONE_LOCATION/public/vendor/jQueryCookie \ $SUNSTONE_LOCATION/public/images \ $SUNSTONE_LOCATION/views" @@ -512,6 +513,7 @@ SUNSTONE_PUBLIC_NEW_VENDOR_JDPICKER_IMAGES:$SUNSTONE_LOCATION/public/vendor/4.0/jdpicker_1.1/images SUNSTONE_PUBLIC_NEW_VENDOR_TIMEPICKER:$SUNSTONE_LOCATION/public/vendor/4.0/ SUNSTONE_PUBLIC_NEW_VENDOR_DATETIMEPICKER:$SUNSTONE_LOCATION/public/vendor/4.0/datetimepicker + SUNSTONE_PUBLIC_VENDOR_JQUERYCOOKIE:$SUNSTONE_LOCATION/public/vendor/jQueryCookie SUNSTONE_PUBLIC_IMAGES_FILES:$SUNSTONE_LOCATION/public/images SUNSTONE_PUBLIC_LOCALE_CA:$SUNSTONE_LOCATION/public/locale/ca SUNSTONE_PUBLIC_LOCALE_CS_CZ:$SUNSTONE_LOCATION/public/locale/cs_CZ @@ -1180,7 +1182,9 @@ src/cloud/common/CloudAuth/SunstoneCloudAuth.rb \ src/cloud/common/CloudAuth/EC2CloudAuth.rb \ src/cloud/common/CloudAuth/X509CloudAuth.rb \ - src/cloud/common/CloudAuth/OpenNebulaCloudAuth.rb" + src/cloud/common/CloudAuth/OpenNebulaCloudAuth.rb \ + src/cloud/common/CloudAuth/SSPCloudAuth.rb \ + src/cloud/common/CloudAuth/ssp_helper.rb" #------------------------------------------------------------------------------- # EC2 Query for OpenNebula @@ -1408,10 +1412,12 @@ SUNSTONE_VIEWS_FILES="src/sunstone/views/index.erb \ src/sunstone/views/login.erb \ src/sunstone/views/_login_standard.erb \ - src/sunstone/views/_login_x509.erb" + src/sunstone/views/_login_x509.erb \ + src/sunstone/views/_login_ssp.erb" SUNSTONE_PUBLIC_JS_FILES="src/sunstone/public/js/layout.js \ src/sunstone/public/js/login.js \ + src/sunstone/public/js/login_ssp.js \ src/sunstone/public/js/sunstone.js \ src/sunstone/public/js/sunstone-util.js \ src/sunstone/public/js/opennebula.js \ @@ -1531,6 +1537,8 @@ src/sunstone/public/vendor/4.0/jquery_layout/layout-default-latest.css \ src/sunstone/public/vendor/4.0/jquery_layout/jquery.layout-latest.min.js" +SUNSTONE_PUBLIC_VENDOR_JQUERYCOOKIE="\ + src/sunstone/public/vendor/jQueryCookie/jquery-cookie.js" SUNSTONE_PUBLIC_NEW_VENDOR_FONTAWESOME_CSS="\ src/sunstone/public/vendor/4.0/fontawesome/css/font-awesome.min.css" diff -uNr one/src/cloud/common/CloudAuth/SSPCloudAuth.rb one_patched/src/cloud/common/CloudAuth/SSPCloudAuth.rb --- one/src/cloud/common/CloudAuth/SSPCloudAuth.rb 1970-01-01 01:00:00.000000000 +0100 +++ one_patched/src/cloud/common/CloudAuth/SSPCloudAuth.rb 2012-12-21 11:53:07.000000000 +0100 @@ -0,0 +1,94 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed 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. # +#--------------------------------------------------------------------------- # + +DIR=File.dirname(__FILE__) +$: << DIR + +require 'ssp_helper.rb' +require 'xmlrpc/client' +require 'rubygems' +require 'nokogiri' +require 'json' +require 'net/http' + +# @mainpage SSP Cloud Auth module for OpenNebula Sunstone +# +# @section desc Description +# This is a new authentication module for OpenNebula Sunstone. In its name SSP means +# Simple SAML PHP (http://simplesamlphp.org/). +# SSP Cloud Auth module is useful, when a SingleSignOn is login needed, which service is realised +# with SimpleSAMLphp. In this case, login handled by SimpleSAMLphp and so the Sunstone +# auth module (this one) makes only the identification of the users. \n +# If new user wants to login, this module creates a new account for the user. +# +# @section conf Configuration +# Configuration file is at the end of the main Sunstone configuration file (sunstone-server.conf). +module SSPCloudAuth + + attr_accessor :sessionid, :session + + @sessionid='' + @session='' + + # original do_auth function + # gets login datas from SimpleSAMLphp and authenticates the user + # if new user wants to login, then creates its user + # updates user's group + # @param params['ssp_sessionid'] SSP session id from cookie + # @return username if authentication success + def do_auth(env, params={}) + auth = Rack::Auth::Basic::Request.new(env) + + # initialize some variable + @sessionid=params['ssp_sessionid'] + + if auth.provided? && auth.basic? + + # create helper + ssp=SSP_Helper.new + + # get login datas from ssp + @session=ssp.get_ssp_session(@sessionid) + + # test if user is authorized + if (@session['is_auth']!=true) + return nil + end + + # get name from session + @username=@session['data']['eduPersonPrincipalName'].join + + # if any privilege was sent then get it; if it was not sent and strict auth needed then deny login + if @session['data'].has_key?('eduPersonEntitlement') + @groupname=@session['data']['eduPersonEntitlement'].join + else + @groupname='' + end + + # if new user wants to login then create it + if ssp.get_userid(@username).empty? + ssp.create_user(@username) + end + + # update user's group + ssp.update_group(@username,@groupname) + + return @username + end + + return nil + end +end diff -uNr one/src/cloud/common/CloudAuth/ssp_helper.rb one_patched/src/cloud/common/CloudAuth/ssp_helper.rb --- one/src/cloud/common/CloudAuth/ssp_helper.rb 1970-01-01 01:00:00.000000000 +0100 +++ one_patched/src/cloud/common/CloudAuth/ssp_helper.rb 2012-12-21 11:53:54.000000000 +0100 @@ -0,0 +1,187 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed 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. # +#--------------------------------------------------------------------------- # + +# Helper class to call methods in SSPCloudAuth module +class SSP_Helper + + attr_accessor :one_xmlrpc, :one_auth, :one_location, :config + + # initalize some instance variable + def initialize + @one_location='' + + # get ssp configuration + if @one_location.empty? + etc_location="/etc/one" + else + etc_location=@one_location+"/etc" + end + + configuration_file=etc_location+"/sunstone-server.conf" + + begin + @config = YAML.load_file(configuration_file) + rescue Exception => e + STDERR.puts "Error parsing config file #{configuration_file}: #{e.message}" + exit 1 + end + + @one_xmlrpc=@config[:one_xmlrpc] + @one_auth=@config[:one_auth_for_ssp] + end + + # creating new user + # @param username username of user to be created + def create_user(username) + server=XMLRPC::Client.new2(@one_xmlrpc) + + session_string=self.get_credential["username"]+":"+self.get_credential["password"] + + begin + response=server.call("one.user.allocate",session_string,username,self.generate_password,'') + rescue Exception => e + [false, e.message] + end + end + + # update user's group or create it's group + # @param username username's group will be updated + # @param groupname user's group + def update_group(username,groupname) + server=XMLRPC::Client.new2(@one_xmlrpc) + + session_string=self.get_credential["username"]+":"+self.get_credential["password"] + + if groupname.empty? + groupname='users' + end + + if self.get_groupid(groupname).empty? + self.create_group(groupname) + end + + begin + response=server.call("one.user.chgrp",session_string,self.get_userid(username).to_i,self.get_groupid(groupname).to_i) + rescue Exception => e + [false, e.message] + end + end + + # get username and password from $ONE_AUTH file + # @return username and password in a Hash + def get_credential + credential=Hash.new + + if File.readable?(@one_auth) + File.open(@one_auth, 'r') do |line| + auth_line=line.gets.strip + auth_line=auth_line.split(':') + + credential["username"]=auth_line[0] + credential["password"]=auth_line[1] + end + else + # TODO: write error into log (SSP_Helper ERROR: $ONE_AUTH file is not readable) + raise "one auth file not readable" + end + return credential + end + + # get user's ID + # @param username username + # @return user's ID + def get_userid(username) + server=XMLRPC::Client.new2(@one_xmlrpc) + + session_string=self.get_credential["username"]+":"+self.get_credential["password"] + + begin + response=server.call("one.userpool.info",session_string) + rescue Exception => e + [false, e.message] + end + + xml=Nokogiri::XML(response[1]) + return xml.xpath('//USER[NAME=\''+username+'\']/ID').inner_text + end + + # get group ID of a group + # @param groupname groupname + # @return group's ID + def get_groupid(groupname) + server=XMLRPC::Client.new2(@one_xmlrpc) + + session_string=self.get_credential["username"]+":"+self.get_credential["password"] + + begin + response=server.call("one.grouppool.info",session_string) + rescue Exception => e + [false, e.message] + end + + xml=Nokogiri::XML(response[1]) + return xml.xpath('//GROUP[NAME=\''+groupname+'\']/ID').inner_text + end + + # creating new group + # @param groupname groupname of group to be created + def create_group(groupname) + server=XMLRPC::Client.new2(@one_xmlrpc) + + session_string=self.get_credential["username"]+":"+self.get_credential["password"] + + begin + response=server.call("one.group.allocate",session_string,groupname) + rescue Exception => e + [false, e.message] + end + end + + # create random password for new users + # @return random password + def generate_password + return rand(36**20).to_s(36) + end + + # get ssp session variable in JSON format + # @param sessionid ssp session id from cookie + # @return ssp_session ssp session in JSON format + def get_ssp_session(sessionid) + url=URI.parse(@config[:ssp_host]) + http=Net::HTTP.new(url.host,url.port) + req=Net::HTTP::Get.new(@config[:ssp_loginvalidator]+sessionid) + if url.scheme=='https' + http.use_ssl=true + http.verify_mode=OpenSSL::SSL::VERIFY_NONE + end + res=http.request(req) + ssp_session_json=res.body + ssp_session=JSON.parse(ssp_session_json) + return ssp_session + end + + # is user authorized in ssp? + # @param sessionid ssp session id from cookie + # @return true if user is authorized + def authorized?(sessionid) + if sessionid.nil? or get_ssp_session(sessionid)['is_auth']!=true + return false + else + return true + end + end + +end diff -uNr one/src/cloud/common/CloudAuth.rb one_patched/src/cloud/common/CloudAuth.rb --- one/src/cloud/common/CloudAuth.rb 2013-06-14 12:22:30.674006109 +0200 +++ one_patched/src/cloud/common/CloudAuth.rb 2013-06-14 13:56:56.527881665 +0200 @@ -21,6 +21,7 @@ AUTH_MODULES = { "occi" => 'OCCICloudAuth', "sunstone" => 'SunstoneCloudAuth' , + "ssp" => 'SSPCloudAuth' , "ec2" => 'EC2CloudAuth', "x509" => 'X509CloudAuth', "opennebula" => 'OpenNebulaCloudAuth' diff -uNr one/src/sunstone/etc/sunstone-server.conf one_patched/src/sunstone/etc/sunstone-server.conf --- one/src/sunstone/etc/sunstone-server.conf 2013-06-14 12:22:30.747007896 +0200 +++ one_patched/src/sunstone/etc/sunstone-server.conf 2013-06-14 13:58:07.563681523 +0200 @@ -124,3 +124,22 @@ #:routes: # - custom # - other + +################################################################################ +## SSP Auth module +################################################################################# +# +## ssp_sessionid: Simple SAML PHP session ID cookie name. +## ssp_host: Simple SAML PHP host url. +## ssp_loginpage: Simple SAML PHP login page. +## ssp_loginvalidator: Simple SAML PHP login validator script path. This +## script authenticates users in Simple SAML PHP and +## gets login data in JSON format. +## ssp_logoutpage: Simple SAML PHP logout page. +## one_auth_for_ssp: one_auth file location +:ssp_sessionid: PHPSESSID +:ssp_host: http://192.168.204.100 +:ssp_loginpage: /simplesaml/module.php/core/as_login.php?AuthId=default-sp&ReturnTo=/one/ +:ssp_loginvalidator: /simplesaml/module.php/getSession/index.php?sessid= +:ssp_logoutpage: /simplesaml/module.php/core/as_logout.php?AuthId=default-sp&ReturnTo=/simplesaml/logout.php +:one_auth_for_ssp: /var/lib/one/.one/one_auth diff -uNr one/src/sunstone/public/js/login_ssp.js one_patched/src/sunstone/public/js/login_ssp.js --- one/src/sunstone/public/js/login_ssp.js 1970-01-01 01:00:00.000000000 +0100 +++ one_patched/src/sunstone/public/js/login_ssp.js 2013-06-14 14:36:43.630852892 +0200 @@ -0,0 +1,100 @@ +/* -------------------------------------------------------------------------- */ +/* Copyright 2002-2013, OpenNebula Project (OpenNebula.org), C12G Labs */ +/* */ +/* Licensed 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 auth_success(req, response){ + window.location.href = "."; +} + +function auth_error(req, error){ + + var status = error.error.http_status; + + switch (status){ + case 401: + $("#error_message").text("Invalid username or password"); + break; + case 500: + $("#error_message").text("OpenNebula is not running or there was a server exception. Please check the server logs."); + break; + case 0: + $("#error_message").text("No answer from server. Is it running?"); + break; + default: + $("#error_message").text("Unexpected error. Status "+status+". Check the server logs."); + }; + $("#error_box").fadeIn("slow"); +} + +function authenticate(){ + var username = ''; + var password = ''; + var remember = true; + + $("#error_box").fadeOut("slow"); + + OpenNebula.Auth.login({ data: {username: username + , password: password} + , remember: remember + , success: auth_success + , error: auth_error + }); +} + +function getInternetExplorerVersion(){ +// Returns the version of Internet Explorer or a -1 +// (indicating the use of another browser). + var rv = -1; // Return value assumes failure. + if (navigator.appName == 'Microsoft Internet Explorer') + { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) + rv = parseFloat( RegExp.$1 ); + } + return rv; +} + +function checkVersion(){ + var ver = getInternetExplorerVersion(); + + if ( ver > -1 ){ + msg = ver <= 7.0 ? "You are using an old version of IE. \ +Please upgrade or use Firefox or Chrome for full compatibility." : + "OpenNebula Sunstone is best seen with Chrome or Firefox"; + $("#error_box").text(msg); + $("#error_box").fadeIn('slow'); + } +} + +$(document).ready(function(){ + var pathname=$(location).attr('href'); + $.ajax({ + type: 'GET', + url:pathname, + complete: function(XMLHttpRequest,textStatus){ + authenticate(); + return false; + } + }); + + //compact login elements according to screen height + if (screen.height <= 600){ + $('div#logo_sunstone').css("top","15px"); + $('.error_message').css("top","10px"); + }; + + checkVersion(); +}); diff -uNr one/src/sunstone/public/js/sunstone.js one_patched/src/sunstone/public/js/sunstone.js --- one/src/sunstone/public/js/sunstone.js 2013-06-14 12:22:30.773008532 +0200 +++ one_patched/src/sunstone/public/js/sunstone.js 2013-06-14 14:06:50.331896879 +0200 @@ -444,15 +444,15 @@ //This variables can be used anywhere switch(whichUI()){ case "sunstone": - username = cookie["one-user"]; + username = decodeURIComponent(cookie["one-user"]); uid = cookie["one-user_id"]; gid = cookie["one-user_gid"]; break; case "ozones": - username = cookie["ozones-user"]; + username = decodeURIComponent(cookie["ozones-user"]); break; case "selfservice": - username = cookie["occi-user"]; + username = decodeURIComponent(cookie["occi-user"]); uid = cookie["occi-user-id"]; break; }; diff -uNr one/src/sunstone/public/vendor/jQueryCookie/jquery-cookie.js one_patched/src/sunstone/public/vendor/jQueryCookie/jquery-cookie.js --- one/src/sunstone/public/vendor/jQueryCookie/jquery-cookie.js 1970-01-01 01:00:00.000000000 +0100 +++ one_patched/src/sunstone/public/vendor/jQueryCookie/jquery-cookie.js 2012-12-11 18:20:46.000000000 +0100 @@ -0,0 +1,62 @@ +/*jshint eqnull:true */ +/*! + * jQuery Cookie Plugin v1.1 + * https://github.com/carhartl/jquery-cookie + * + * Copyright 2011, Klaus Hartl + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://www.opensource.org/licenses/mit-license.php + * http://www.opensource.org/licenses/GPL-2.0 + */ +(function($, document) { + + var pluses = /\+/g; + function raw(s) { + return s; + } + function decoded(s) { + return decodeURIComponent(s.replace(pluses, ' ')); + } + + $.cookie = function(key, value, options) { + + // key and at least value given, set cookie... + if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value == null)) { + options = $.extend({}, $.cookie.defaults, options); + + if (value == null) { + options.expires = -1; + } + + if (typeof options.expires === 'number') { + var days = options.expires, t = options.expires = new Date(); + t.setDate(t.getDate() + days); + } + + value = String(value); + + return (document.cookie = [ + encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value), + options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE + options.path ? '; path=' + options.path : '', + options.domain ? '; domain=' + options.domain : '', + options.secure ? '; secure' : '' + ].join('')); + } + + // key and possibly options given, get cookie... + options = value || $.cookie.defaults || {}; + var decode = options.raw ? raw : decoded; + var cookies = document.cookie.split('; '); + for (var i = 0, parts; (parts = cookies[i] && cookies[i].split('=')); i++) { + if (decode(parts.shift()) === key) { + return decode(parts.join('=')); + } + } + return null; + }; + + $.cookie.defaults = {}; + +})(jQuery, document); + diff -uNr one/src/sunstone/sunstone-server.rb one_patched/src/sunstone/sunstone-server.rb --- one/src/sunstone/sunstone-server.rb 2013-06-14 12:22:30.805009316 +0200 +++ one_patched/src/sunstone/sunstone-server.rb 2013-06-14 14:18:14.552098832 +0200 @@ -43,6 +43,7 @@ $: << RUBY_LIB_LOCATION $: << RUBY_LIB_LOCATION+'/cloud' +$: << RUBY_LIB_LOCATION+'/cloud/CloudAuth' $: << SUNSTONE_ROOT_DIR $: << SUNSTONE_ROOT_DIR+'/models' @@ -60,6 +61,7 @@ require 'SunstoneServer' require 'SunstoneViews' +require 'ssp_helper' ############################################################################## # Configuration @@ -139,6 +141,10 @@ def build_session begin + if $conf[:auth]=='ssp' + response.set_cookie('ssp_logoutpage',$conf[:ssp_host]+$conf[:ssp_logoutpage]) + params['ssp_sessionid']=request.cookies[$conf[:ssp_sessionid]] + end result = $cloud_auth.auth(request.env, params) rescue Exception => e logger.error { e.message } @@ -247,6 +253,15 @@ ############################################################################## get '/' do content_type 'text/html', :charset => 'utf-8' + + if $conf[:auth]=='ssp' + ssp_sessionid=request.cookies[$conf[:ssp_sessionid]] + ssp=SSP_Helper.new + if not ssp.authorized?(ssp_sessionid) + redirect $conf[:ssp_host]+$conf[:ssp_loginpage], 302 + end + end + if !authorized? return erb :login end diff -uNr one/src/sunstone/views/index.erb one_patched/src/sunstone/views/index.erb --- one/src/sunstone/views/index.erb 2013-06-14 12:22:30.808009389 +0200 +++ one_patched/src/sunstone/views/index.erb 2013-06-14 14:19:15.459626923 +0200 @@ -8,6 +8,7 @@ + diff -uNr one/src/sunstone/views/login.erb one_patched/src/sunstone/views/login.erb --- one/src/sunstone/views/login.erb 2013-06-14 12:22:30.808009389 +0200 +++ one_patched/src/sunstone/views/login.erb 2013-06-14 14:21:30.916023544 +0200 @@ -11,7 +11,11 @@ - + <% if settings.config[:auth] == "ssp" %> + + <% else %> + + <% end %> @@ -20,6 +24,8 @@ <% if settings.config[:auth] == "x509" %> <%= erb :_login_x509 %> +<% elsif settings.config[:auth] == "ssp" %> + <%= erb :_login_ssp %> <% else %> <%= erb :_login_standard %> <% end %> diff -uNr one/src/sunstone/views/_login_ssp.erb one_patched/src/sunstone/views/_login_ssp.erb --- one/src/sunstone/views/_login_ssp.erb 1970-01-01 01:00:00.000000000 +0100 +++ one_patched/src/sunstone/views/_login_ssp.erb 2013-06-14 14:24:23.678352259 +0200 @@ -0,0 +1,11 @@ +