refstack/refstack/api/controllers/auth.py

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)