171 lines
7.0 KiB
Python
171 lines
7.0 KiB
Python
# Copyright (c) 2015 Mirantis, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
"""Authentication controller."""
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
import pecan
|
|
from pecan import rest
|
|
from six.moves.urllib import parse
|
|
|
|
from refstack.api import constants as const
|
|
from refstack.api import utils as api_utils
|
|
from refstack import db
|
|
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
OPENID_OPTS = [
|
|
cfg.StrOpt('openstack_openid_endpoint',
|
|
default='https://openstackid.org/accounts/openid2',
|
|
help='OpenStackID Auth Server URI.'
|
|
),
|
|
cfg.StrOpt('openid_mode',
|
|
default='checkid_setup',
|
|
help='Interaction mode. Specifies whether Openstack Id '
|
|
'IdP may interact with the user to determine the '
|
|
'outcome of the request.'
|
|
),
|
|
cfg.StrOpt('openid_ns',
|
|
default='http://specs.openid.net/auth/2.0',
|
|
help='Protocol version. Value identifying the OpenID '
|
|
'protocol version being used. This value should '
|
|
'be "http://specs.openid.net/auth/2.0".'
|
|
),
|
|
cfg.StrOpt('openid_return_to',
|
|
default='/v1/auth/signin_return',
|
|
help='Return endpoint in Refstack\'s API. Value indicating '
|
|
'the endpoint where the user should be returned to after '
|
|
'signing in. Openstack Id Idp only supports HTTPS '
|
|
'address types.'
|
|
),
|
|
cfg.StrOpt('openid_claimed_id',
|
|
default='http://specs.openid.net/auth/2.0/identifier_select',
|
|
help='Claimed identifier. This value must be set to '
|
|
'"http://specs.openid.net/auth/2.0/identifier_select". '
|
|
'or to user claimed identity (user local identifier '
|
|
'or user owned identity [ex: custom html hosted on a '
|
|
'owned domain set to html discover]).'
|
|
),
|
|
cfg.StrOpt('openid_identity',
|
|
default='http://specs.openid.net/auth/2.0/identifier_select',
|
|
help='Alternate identifier. This value must be set to '
|
|
'http://specs.openid.net/auth/2.0/identifier_select.'
|
|
),
|
|
cfg.StrOpt('openid_ns_sreg',
|
|
default='http://openid.net/extensions/sreg/1.1',
|
|
help='Indicates request for user attribute information. '
|
|
'This value must be set to '
|
|
'"http://openid.net/extensions/sreg/1.1".'
|
|
),
|
|
cfg.StrOpt('openid_sreg_required',
|
|
default='email,fullname',
|
|
help='Comma-separated list of field names which, '
|
|
'if absent from the response, will prevent the '
|
|
'Consumer from completing the registration without '
|
|
'End User interation. The field names are those that '
|
|
'are specified in the Response Format, with the '
|
|
'"openid.sreg." prefix removed. Valid values include: '
|
|
'"country", "email", "firstname", "language", "lastname"'
|
|
)
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
opt_group = cfg.OptGroup(name='osid',
|
|
title='Options for the Refstack OpenID 2.0 through '
|
|
'Openstack Authentication Server')
|
|
CONF.register_group(opt_group)
|
|
CONF.register_opts(OPENID_OPTS, opt_group)
|
|
|
|
|
|
class AuthController(rest.RestController):
|
|
|
|
"""Controller provides user authentication in OpenID 2.0 IdP."""
|
|
|
|
_custom_actions = {
|
|
"signin": ["GET"],
|
|
"signin_return": ["GET"],
|
|
"signout": ["GET"]
|
|
}
|
|
|
|
@pecan.expose()
|
|
def signin(self):
|
|
"""Handle signin request."""
|
|
session = api_utils.get_user_session()
|
|
if api_utils.is_authenticated():
|
|
pecan.redirect(CONF.ui_url)
|
|
else:
|
|
api_utils.delete_params_from_user_session([const.USER_OPENID])
|
|
|
|
csrf_token = api_utils.get_token()
|
|
session[const.CSRF_TOKEN] = csrf_token
|
|
session.save()
|
|
return_endpoint = parse.urljoin(CONF.api.api_url,
|
|
CONF.osid.openid_return_to)
|
|
return_to = api_utils.set_query_params(return_endpoint,
|
|
{const.CSRF_TOKEN: csrf_token})
|
|
|
|
params = {
|
|
const.OPENID_MODE: CONF.osid.openid_mode,
|
|
const.OPENID_NS: CONF.osid.openid_ns,
|
|
const.OPENID_RETURN_TO: return_to,
|
|
const.OPENID_CLAIMED_ID: CONF.osid.openid_claimed_id,
|
|
const.OPENID_IDENTITY: CONF.osid.openid_identity,
|
|
const.OPENID_REALM: CONF.api.api_url,
|
|
const.OPENID_NS_SREG: CONF.osid.openid_ns_sreg,
|
|
const.OPENID_NS_SREG_REQUIRED: CONF.osid.openid_sreg_required,
|
|
}
|
|
url = CONF.osid.openstack_openid_endpoint
|
|
url = api_utils.set_query_params(url, params)
|
|
pecan.redirect(location=url)
|
|
|
|
@pecan.expose()
|
|
def signin_return(self):
|
|
"""Handle returned request from OpenID 2.0 IdP."""
|
|
session = api_utils.get_user_session()
|
|
if pecan.request.GET.get(const.OPENID_ERROR):
|
|
api_utils.delete_params_from_user_session([const.CSRF_TOKEN])
|
|
pecan.abort(401, pecan.request.GET.get(const.OPENID_ERROR))
|
|
|
|
if pecan.request.GET.get(const.OPENID_MODE) == 'cancel':
|
|
api_utils.delete_params_from_user_session([const.CSRF_TOKEN])
|
|
pecan.abort(401, 'Authentication canceled.')
|
|
|
|
session_token = session.get(const.CSRF_TOKEN)
|
|
request_token = pecan.request.GET.get(const.CSRF_TOKEN)
|
|
if request_token != session_token:
|
|
api_utils.delete_params_from_user_session([const.CSRF_TOKEN])
|
|
pecan.abort(401, 'Authentication is failed. Try again.')
|
|
|
|
api_utils.verify_openid_request(pecan.request)
|
|
user_info = {
|
|
'openid': pecan.request.GET.get(const.OPENID_CLAIMED_ID),
|
|
'email': pecan.request.GET.get(const.OPENID_NS_SREG_EMAIL),
|
|
'fullname': pecan.request.GET.get(const.OPENID_NS_SREG_FULLNAME)
|
|
}
|
|
user = db.user_update_or_create(user_info)
|
|
|
|
api_utils.delete_params_from_user_session([const.CSRF_TOKEN])
|
|
session[const.USER_OPENID] = user.openid
|
|
session.save()
|
|
|
|
pecan.redirect(CONF.ui_url)
|
|
|
|
@pecan.expose()
|
|
def signout(self):
|
|
"""Handle signout request."""
|
|
if api_utils.is_authenticated():
|
|
api_utils.delete_params_from_user_session([const.USER_OPENID])
|
|
pecan.redirect(CONF.ui_url)
|