Completed
Pull Request — master (#2920)
by Anthony
07:49 queued 02:41
created

AuthHandlerBase   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 7
Duplicated Lines 0 %

Importance

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

2 Methods

Rating   Name   Duplication   Size   Complexity  
A handle_auth() 0 2 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, **kwargs):
36
        raise NotImplementedError()
37
38
    def _create_token_for_user(self, username, ttl=None):
39
        tokendb = create_token(username=username, ttl=ttl)
40
        return TokenAPI.from_model(tokendb)
41
42
43
class ProxyAuthHandler(AuthHandlerBase):
44
    def handle_auth(self, request, **kwargs):
45
        remote_addr = pecan.request.headers.get('x-forwarded-for',
46
                                                pecan.request.remote_addr)
47
        extra = {'remote_addr': remote_addr}
48
49
        if pecan.request.remote_user:
50
            ttl = getattr(request, 'ttl', None)
51
            try:
52
                token = self._create_token_for_user(username=pecan.request.remote_user,
53
                                                    ttl=ttl)
54
            except TTLTooLargeException as e:
55
                abort_request(status_code=http_client.BAD_REQUEST,
56
                              message=e.message)
57
            return token
58
59
        LOG.audit('Access denied to anonymous user.', extra=extra)
60
        abort_request()
61
62
63
class StandaloneAuthHandler(AuthHandlerBase):
64
    def __init__(self, *args, **kwargs):
65
        self._auth_backend = get_backend_instance(name=cfg.CONF.auth.backend)
66
        super(StandaloneAuthHandler, self).__init__(*args, **kwargs)
67
68
    def handle_auth(self, request, **kwargs):
69
        authorization = pecan.request.authorization
70
71
        auth_backend = self._auth_backend.__class__.__name__
72
        remote_addr = pecan.request.remote_addr
73
        extra = {'auth_backend': auth_backend, 'remote_addr': remote_addr}
74
75
        if not authorization:
76
            LOG.audit('Authorization header not provided', extra=extra)
77
            abort_request()
78
            return
79
80
        auth_type, auth_value = authorization
81
        if auth_type.lower() not in ['basic']:
82
            extra['auth_type'] = auth_type
83
            LOG.audit('Unsupported authorization type: %s' % (auth_type), extra=extra)
84
            abort_request()
85
            return
86
87
        try:
88
            auth_value = base64.b64decode(auth_value)
89
        except Exception:
90
            LOG.audit('Invalid authorization header', extra=extra)
91
            abort_request()
92
            return
93
94
        split = auth_value.split(':')
95
        if len(split) != 2:
96
            LOG.audit('Invalid authorization header', extra=extra)
97
            abort_request()
98
            return
99
100
        username, password = split
101
        result = self._auth_backend
102
103
        result = self._auth_backend.authenticate(username=username, password=password)
104
        if result is True:
105
            LOG.audit(request.body)
106
            ttl = getattr(request, 'ttl', None)
107
            impersonate_user = getattr(request.body, 'user', None)
108
109
            if impersonate_user is not None:
110
                # check this is a service account
111
                if not User.get_by_name(username).is_service:
112
                    message = "Current user is not a service and cannot " \
113
                              "request impersonated tokens"
114
                    abort_request(status_code=http_client.BAD_REQUEST,
115
                                  message=message)
116
                    return
117
                username = impersonate_user
118
            else:
119
                impersonate_user = getattr(request, 'impersonate_user', None)
120
                nickname_origin = getattr(request, 'nickname_origin', None)
121
                if impersonate_user is not None:
122
                    try:
123
                        # check this is a service account
124
                        if not User.get_by_name(username).is_service:
125
                            raise NotServiceUserError()
126
                        username = User.get_by_nickname(impersonate_user,
127
                                                        nickname_origin).name
128
                    except NotServiceUserError:
129
                        message = "Current user is not a service and cannot " \
130
                                  "request impersonated tokens"
131
                        abort_request(status_code=http_client.BAD_REQUEST,
132
                                      message=message)
133
                        return
134
                    except UserNotFoundError:
135
                        message = "Could not locate user %s@%s" % \
136
                                  (impersonate_user, nickname_origin)
137
                        abort_request(status_code=http_client.BAD_REQUEST,
138
                                      message=message)
139
                        return
140
                    except NoNicknameOriginProvidedError:
141
                        message = "Nickname origin is not provided for nickname '%s'" % \
142
                                  impersonate_user
143
                        abort_request(status_code=http_client.BAD_REQUEST,
144
                                      message=message)
145
                        return
146
                    except AmbiguousUserError:
147
                        message = "%s@%s matched more than one username" % \
148
                                  (impersonate_user, nickname_origin)
149
                        abort_request(status_code=http_client.BAD_REQUEST,
150
                                      message=message)
151
                        return
152
            try:
153
                token = self._create_token_for_user(
154
                    username=username, ttl=ttl)
155
                return token
156
            except TTLTooLargeException as e:
157
                abort_request(status_code=http_client.BAD_REQUEST,
158
                              message=e.message)
159
                return
160
161
        LOG.audit('Invalid credentials provided', extra=extra)
162
        abort_request()
163