Completed
Pull Request — master (#2920)
by Anthony
04:41
created

AuthHandlerBase   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 8
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 8
rs 10
wmc 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
A handle_auth() 0 3 1
A _create_token_for_user() 0 3 1
1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import pecan
17
import base64
18
import logging
19
from six.moves import http_client
20
from oslo_config import cfg
21
22
from st2common.exceptions.auth import TTLTooLargeException, UserNotFoundError
23
from st2common.exceptions.auth import NoNicknameOriginProvidedError, AmbiguousUserError
24
from st2common.exceptions.auth import NotServiceUserError
25
from st2common.persistence.auth import User
26
from st2common.util.pecan_util import abort_request
27
from st2common.services.access import create_token
28
from st2common.models.api.auth import TokenAPI
29
from st2auth.backends import get_backend_instance
30
31
LOG = logging.getLogger(__name__)
32
33
34
class AuthHandlerBase(object):
35
    def handle_auth(self, request, headers=None, remote_addr=None,
36
                    remote_user=None, **kwargs):
37
        raise NotImplementedError()
38
39
    def _create_token_for_user(self, username, ttl=None):
40
        tokendb = create_token(username=username, ttl=ttl)
41
        return TokenAPI.from_model(tokendb)
42
43
44
class ProxyAuthHandler(AuthHandlerBase):
45
    def handle_auth(self, request, headers=None, remote_addr=None,
46
                    remote_user=None, **kwargs):
47
        remote_addr = headers.get('x-forwarded-for',
48
                                  remote_addr)
49
        extra = {'remote_addr': remote_addr}
50
51
        if remote_user:
52
            ttl = getattr(request, 'ttl', None)
53
            try:
54
                token = self._create_token_for_user(username=remote_user,
55
                                                    ttl=ttl)
56
            except TTLTooLargeException as e:
57
                abort_request(status_code=http_client.BAD_REQUEST,
58
                              message=e.message)
59
            return token
60
61
        LOG.audit('Access denied to anonymous user.', extra=extra)
62
        abort_request()
63
64
65
class StandaloneAuthHandler(AuthHandlerBase):
66
    def __init__(self, *args, **kwargs):
67
        self._auth_backend = get_backend_instance(name=cfg.CONF.auth.backend)
68
        super(StandaloneAuthHandler, self).__init__(*args, **kwargs)
69
70
    def handle_auth(self, request, headers=None, remote_addr=None, remote_user=None,
71
                    **kwargs):
72
        authorization = pecan.request.authorization
73
74
        auth_backend = self._auth_backend.__class__.__name__
75
76
        extra = {'auth_backend': auth_backend, 'remote_addr': remote_addr}
77
78
        if not authorization:
79
            LOG.audit('Authorization header not provided', extra=extra)
80
            abort_request()
81
            return
82
83
        auth_type, auth_value = authorization
84
        if auth_type.lower() not in ['basic']:
85
            extra['auth_type'] = auth_type
86
            LOG.audit('Unsupported authorization type: %s' % (auth_type), extra=extra)
87
            abort_request()
88
            return
89
90
        try:
91
            auth_value = base64.b64decode(auth_value)
92
        except Exception:
93
            LOG.audit('Invalid authorization header', extra=extra)
94
            abort_request()
95
            return
96
97
        split = auth_value.split(':')
98
        if len(split) != 2:
99
            LOG.audit('Invalid authorization header', extra=extra)
100
            abort_request()
101
            return
102
103
        username, password = split
104
        result = self._auth_backend
105
106
        result = self._auth_backend.authenticate(username=username, password=password)
107
        if result is True:
108
            LOG.audit(request.body)
109
            ttl = getattr(request, 'ttl', None)
110
            impersonate_user = getattr(request.body, 'user', None)
111
112
            if impersonate_user is not None:
113
                # check this is a service account
114
                if not User.get_by_name(username).is_service:
115
                    message = "Current user is not a service and cannot " \
116
                              "request impersonated tokens"
117
                    abort_request(status_code=http_client.BAD_REQUEST,
118
                                  message=message)
119
                    return
120
                username = impersonate_user
121
            else:
122
                impersonate_user = getattr(request, 'impersonate_user', None)
123
                nickname_origin = getattr(request, 'nickname_origin', None)
124
                if impersonate_user is not None:
125
                    try:
126
                        # check this is a service account
127
                        if not User.get_by_name(username).is_service:
128
                            raise NotServiceUserError()
129
                        username = User.get_by_nickname(impersonate_user,
130
                                                        nickname_origin).name
131
                    except NotServiceUserError:
132
                        message = "Current user is not a service and cannot " \
133
                                  "request impersonated tokens"
134
                        abort_request(status_code=http_client.BAD_REQUEST,
135
                                      message=message)
136
                        return
137
                    except UserNotFoundError:
138
                        message = "Could not locate user %s@%s" % \
139
                                  (impersonate_user, nickname_origin)
140
                        abort_request(status_code=http_client.BAD_REQUEST,
141
                                      message=message)
142
                        return
143
                    except NoNicknameOriginProvidedError:
144
                        message = "Nickname origin is not provided for nickname '%s'" % \
145
                                  impersonate_user
146
                        abort_request(status_code=http_client.BAD_REQUEST,
147
                                      message=message)
148
                        return
149
                    except AmbiguousUserError:
150
                        message = "%s@%s matched more than one username" % \
151
                                  (impersonate_user, nickname_origin)
152
                        abort_request(status_code=http_client.BAD_REQUEST,
153
                                      message=message)
154
                        return
155
            try:
156
                token = self._create_token_for_user(
157
                    username=username, ttl=ttl)
158
                return token
159
            except TTLTooLargeException as e:
160
                abort_request(status_code=http_client.BAD_REQUEST,
161
                              message=e.message)
162
                return
163
164
        LOG.audit('Invalid credentials provided', extra=extra)
165
        abort_request()
166