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

StandaloneAuthHandler   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 100
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 100
rs 10
wmc 16

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 3 1
F handle_auth() 0 95 15
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 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