def test_auth_saml(self): """ MULTI VO (REST): Test saml authentication to multiple VOs """ mw = [] try: add_account_identity('ddmlab', 'SAML', 'root', '*****@*****.**', 'root', **self.vo) add_account_identity('ddmlab', 'SAML', 'root', '*****@*****.**', 'root', **self.new_vo) except Duplicate: pass # Might already exist, can skip # Can't rely on `onelogin` module being present, so get tokens from API instead token_tst = get_auth_token_saml('root', 'ddmlab', 'unknown', None, **self.vo).token token_new = get_auth_token_saml('root', 'ddmlab', 'unknown', None, **self.new_vo).token headers_tst = {'X-Rucio-Auth-Token': str(token_tst)} res_tst = TestApp(account_app.wsgifunc(*mw)).get('/', headers=headers_tst, expect_errors=True) assert_equal(res_tst.status, 200) accounts_tst = [parse_response(a)['account'] for a in res_tst.body.decode().split('\n')[:-1]] assert_not_equal(len(accounts_tst), 0) assert_in(self.account_tst, accounts_tst) assert_not_in(self.account_new, accounts_tst) headers_new = {'X-Rucio-Auth-Token': str(token_new)} res_new = TestApp(account_app.wsgifunc(*mw)).get('/', headers=headers_new, expect_errors=True) assert_equal(res_new.status, 200) accounts_new = [parse_response(a)['account'] for a in res_new.body.decode().split('\n')[:-1]] assert_not_equal(len(accounts_new), 0) assert_in(self.account_new, accounts_new) assert_not_in(self.account_tst, accounts_new)
def test_get_auth_token_saml_fail(self): """AUTHENTICATION (CORE): SAML NameID (wrong credentials).""" root = InternalAccount('root', **self.vo) try: add_account_identity('ddmlab', IdentityType.SAML, root, email='*****@*****.**') except Duplicate: pass # might already exist, can skip with assert_raises(AccessDenied): get_auth_token_saml(account='root', saml_nameid='not_ddmlab', appid='test', ip='127.0.0.1', **self.vo) del_account_identity('ddmlab', IdentityType.SAML, root)
def get(self): """ .. :quickref: SAML; :status 200: OK :status 401: Unauthorized :reqheader Rucio-VO: VO name as a string (Multi-VO only) :reqheader Rucio-Account: Account identifier as a string. :reqheader Rucio-Username: Username as a string. :reqheader Rucio-Password: Password as a string. :reqheader Rucio-AppID: Application identifier as a string. :resheader X-Rucio-SAML-Auth-URL: as a variable-length string header. """ headers = Headers() headers.set('Access-Control-Allow-Origin', request.environ.get('HTTP_ORIGIN')) headers.set('Access-Control-Allow-Headers', request.environ.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')) headers.set('Access-Control-Allow-Methods', '*') headers.set('Access-Control-Allow-Credentials', 'true') headers.set('Access-Control-Expose-Headers', 'X-Rucio-Auth-Token') headers.set('Content-Type', 'application/octet-stream') headers.set('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate') headers.add('Cache-Control', 'post-check=0, pre-check=0') headers.set('Pragma', 'no-cache') if not EXTRA_MODULES['onelogin']: return "SAML not configured on the server side.", 400, headers saml_nameid = cookies().get('saml-nameid') vo = request.headers.get('X-Rucio-VO', default='def') account = request.headers.get('X-Rucio-Account', default=None) appid = request.headers.get('X-Rucio-AppID', default='unknown') ip = request.headers.get('X-Forwarded-For', default=request.remote_addr) if saml_nameid: try: result = get_auth_token_saml(account, saml_nameid, appid, ip, vo=vo) except AccessDenied: return generate_http_error_flask(401, 'CannotAuthenticate', 'Cannot authenticate to account %(account)s with given credentials' % locals(), headers=headers) except RucioException as error: return generate_http_error_flask(500, error.__class__.__name__, error.args[0], headers=headers) except Exception as error: logging.exception("Internal Error") return str(error), 500, headers if not result: return generate_http_error_flask(401, 'CannotAuthenticate', 'Cannot authenticate to account %(account)s with given credentials' % locals(), headers=headers) headers.set('X-Rucio-Auth-Token', result.token) headers.set('X-Rucio-Auth-Token-Expires', date_to_str(result.expired_at)) return '', 200, headers # Path to the SAML config folder SAML_PATH = config_get('saml', 'config_path') req = prepare_saml_request(request.environ, dict(request.args.items(multi=False))) auth = OneLogin_Saml2_Auth(req, custom_base_path=SAML_PATH) headers.set('X-Rucio-SAML-Auth-URL', auth.login()) return '', 200, headers
def get(self): """ .. :quickref: SAML; :status 200: OK :status 401: Unauthorized :reqheader Rucio-VO: VO name as a string (Multi-VO only) :reqheader Rucio-Account: Account identifier as a string. :reqheader Rucio-Username: Username as a string. :reqheader Rucio-Password: Password as a string. :reqheader Rucio-AppID: Application identifier as a string. :resheader X-Rucio-SAML-Auth-URL: as a variable-length string header. """ headers = self.get_headers() headers.set('Content-Type', 'application/octet-stream') headers.set('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate') headers.add('Cache-Control', 'post-check=0, pre-check=0') headers.set('Pragma', 'no-cache') if not EXTRA_MODULES['onelogin']: return "SAML not configured on the server side.", 400, headers saml_nameid = request.cookies.get('saml-nameid', default=None) vo = request.headers.get('X-Rucio-VO', default='def') account = request.headers.get('X-Rucio-Account', default=None) appid = request.headers.get('X-Rucio-AppID', default='unknown') ip = request.headers.get('X-Forwarded-For', default=request.remote_addr) if saml_nameid: try: result = get_auth_token_saml(account, saml_nameid, appid, ip, vo=vo) except AccessDenied: return generate_http_error_flask( status_code=401, exc=CannotAuthenticate.__name__, exc_msg=f'Cannot authenticate to account {account} with given credentials', headers=headers ) if not result: return generate_http_error_flask( status_code=401, exc=CannotAuthenticate.__name__, exc_msg=f'Cannot authenticate to account {account} with given credentials', headers=headers ) headers.set('X-Rucio-Auth-Token', result.token) headers.set('X-Rucio-Auth-Token-Expires', date_to_str(result.expired_at)) return '', 200, headers # Path to the SAML config folder SAML_PATH = config_get('saml', 'config_path') req = prepare_saml_request(request.environ, dict(request.args.items(multi=False))) auth = OneLogin_Saml2_Auth(req, custom_base_path=SAML_PATH) headers.set('X-Rucio-SAML-Auth-URL', auth.login()) return '', 200, headers
def test_get_auth_token_saml_success(self): """AUTHENTICATION (CORE): SAML NameID (correct credentials).""" root = InternalAccount('root', **self.vo) try: add_account_identity('ddmlab', IdentityType.SAML, root, email='*****@*****.**') except Duplicate: pass # might already exist, can skip try: result = get_auth_token_saml(account='root', saml_nameid='ddmlab', appid='test', ip='127.0.0.1', **self.vo) assert_is_not_none(result) except: # FIXME: The WebUI isn't linked to CERN SSO yet so this needs to be fixed once it is linked pass del_account_identity('ddmlab', IdentityType.SAML, root)
def test_get_auth_token_saml_success(self): """AUTHENTICATION (CORE): SAML NameID (correct credentials).""" root = InternalAccount('root', **self.vo) try: add_account_identity('ddmlab', IdentityType.SAML, root, email='*****@*****.**') except Duplicate: pass # might already exist, can skip result = get_auth_token_saml(account='root', saml_nameid='ddmlab', appid='test', ip='127.0.0.1', **self.vo) assert result is not None del_account_identity('ddmlab', IdentityType.SAML, root)
def GET(self): """ HTTP Success: 200 OK HTTP Error: 401 Unauthorized :param Rucio-VO: VO name as a string (Multi-VO only) :param Rucio-Account: Account identifier as a string. :param Rucio-Username: Username as a string. :param Rucio-Password: Password as a string. :param Rucio-AppID: Application identifier as a string. :returns: "X-Rucio-SAML-Auth-URL" as a variable-length string header. """ header('Access-Control-Allow-Origin', ctx.env.get('HTTP_ORIGIN')) header('Access-Control-Allow-Headers', ctx.env.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')) header('Access-Control-Allow-Methods', '*') header('Access-Control-Allow-Credentials', 'true') header('Access-Control-Expose-Headers', 'X-Rucio-Auth-Token') header('Content-Type', 'application/octet-stream') header('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate') header('Cache-Control', 'post-check=0, pre-check=0', False) header('Pragma', 'no-cache') if not EXTRA_MODULES['onelogin']: header('X-Rucio-Auth-Token', None) return "SAML not configured on the server side." saml_nameid = cookies().get('saml-nameid') vo = ctx.env.get('HTTP_X_RUCIO_VO', 'def') account = ctx.env.get('HTTP_X_RUCIO_ACCOUNT') appid = ctx.env.get('HTTP_X_RUCIO_APPID') if appid is None: appid = 'unknown' ip = ctx.env.get('HTTP_X_FORWARDED_FOR') if ip is None: ip = ctx.ip if saml_nameid: try: result = get_auth_token_saml(account, saml_nameid, appid, ip, vo=vo) except AccessDenied: raise generate_http_error( 401, 'CannotAuthenticate', 'Cannot authenticate to account %(account)s with given credentials' % locals()) except RucioException as error: raise generate_http_error(500, error.__class__.__name__, error.args[0]) except Exception as error: print(format_exc()) raise InternalError(error) if not result: raise generate_http_error( 401, 'CannotAuthenticate', 'Cannot authenticate to account %(account)s with given credentials' % locals()) header('X-Rucio-Auth-Token', result.token) header('X-Rucio-Auth-Token-Expires', date_to_str(result.expired_at)) return str() # Path to the SAML config folder SAML_PATH = config_get('saml', 'config_path') request = ctx.env data = dict(param_input()) req = prepare_saml_request(request, data) auth = OneLogin_Saml2_Auth(req, custom_base_path=SAML_PATH) header('X-Rucio-SAML-Auth-URL', auth.login()) return str()
def saml_authentication(method, rendered_tpl): """ Login with SAML :param method: method type, GET or POST :param rendered_tpl: page to be rendered """ attribs = None token = None js_token = "" js_account = "" def_account = None accounts = None cookie_accounts = None rucio_ui_version = version.version_string() policy = config_get('policy', 'permission') # Initialize variables for sending SAML request SAML_PATH = join(dirname(__file__), 'saml/') request = ctx.env data = dict(input()) req = prepare_webpy_request(request, data) auth = OneLogin_Saml2_Auth(req, custom_base_path=SAML_PATH) saml_user_data = cookies().get('saml-user-data') render = template.render(join(dirname(__file__), '../templates')) session_token = cookies().get('x-rucio-auth-token') validate_token = authentication.validate_auth_token(session_token) if method == "GET": # If user data is not present, redirect to IdP for authentication if not saml_user_data: return seeother(auth.login()) # If user data is present and token is valid, render the required page elif validate_token: js_token = __to_js('token', session_token) js_account = __to_js('account', def_account) return render.base(js_token, js_account, rucio_ui_version, policy, rendered_tpl) # If user data is present but token is not valid, create a new one saml_nameid = cookies().get('saml-nameid') accounts = identity.list_accounts_for_identity(saml_nameid, 'saml') cookie_accounts = accounts try: token = authentication.get_auth_token_saml( def_account, saml_nameid, 'webui', ctx.env.get('REMOTE_ADDR')).token except: return render.problem('Cannot get auth token') attribs = list_account_attributes(def_account) # write the token and account to javascript variables, that will be used in the HTML templates. js_token = __to_js('token', token) js_account = __to_js('account', def_account) set_cookies(token, cookie_accounts, attribs) return render.base(js_token, js_account, rucio_ui_version, policy, rendered_tpl) # If method is POST, check the received SAML response and redirect to home if valid auth.process_response() errors = auth.get_errors() if not errors: if auth.is_authenticated(): setcookie('saml-user-data', value=auth.get_attributes(), path='/') setcookie('saml-session-index', value=auth.get_session_index(), path='/') setcookie('saml-nameid', value=auth.get_nameid(), path='/') saml_nameid = auth.get_nameid() accounts = identity.list_accounts_for_identity(saml_nameid, 'saml') cookie_accounts = accounts # try to set the default account to the user account, if not available take the first account. def_account = accounts[0] for account in accounts: account_info = get_account_info(account) if account_info.account_type == AccountType.USER: def_account = account break selected_account = cookies().get('rucio-selected-account') if (selected_account): def_account = selected_account try: token = authentication.get_auth_token_saml( def_account, saml_nameid, 'webui', ctx.env.get('REMOTE_ADDR')).token except: return render.problem('Cannot get auth token') attribs = list_account_attributes(def_account) # write the token and account to javascript variables, that will be used in the HTML templates. js_token = __to_js('token', token) js_account = __to_js('account', def_account) set_cookies(token, cookie_accounts, attribs) return seeother("/") return render.problem("Not authenticated") return render.problem("Error while processing SAML")