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

TokenController._handle_standalone_auth()   F

Complexity

Conditions 12

Size

Total Lines 76

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
c 1
b 0
f 0
dl 0
loc 76
rs 2.1654

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like TokenController._handle_standalone_auth() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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 base64
17
18
import pecan
19
from pecan import rest
20
from six.moves import http_client
21
from oslo_config import cfg
22
23
from st2common.exceptions.auth import TokenNotFoundError, TokenExpiredError
24
from st2common.exceptions.auth import TTLTooLargeException, UserNotFoundError
25
from st2common.exceptions.auth import NoNicknameOriginProvidedError, AmbiguousUserError
26
from st2common.models.api.base import jsexpose
27
from st2common.models.api.auth import TokenAPI
28
from st2common.persistence.auth import User
29
from st2common.services.access import create_token
30
from st2common.util import auth as auth_utils
31
from st2common import log as logging
32
from st2auth.backends import get_backend_instance
33
34
35
LOG = logging.getLogger(__name__)
36
37
38
class TokenValidationController(rest.RestController):
39
40
    @jsexpose(body_cls=TokenAPI, status_code=http_client.OK)
41
    def post(self, request, **kwargs):
42
        token = getattr(request, 'token', None)
43
44
        if not token:
45
            pecan.abort(http_client.BAD_REQUEST, 'Token is not provided.')
46
47
        try:
48
            return {'valid': auth_utils.validate_token(token) is not None}
49
        except (TokenNotFoundError, TokenExpiredError):
50
            return {'valid': False}
51
        except Exception:
52
            msg = 'Unexpected error occurred while verifying token.'
53
            LOG.exception(msg)
54
            pecan.abort(http_client.INTERNAL_SERVER_ERROR, msg)
55
56
57
class TokenController(rest.RestController):
58
    validate = TokenValidationController()
59
60
    def __init__(self, *args, **kwargs):
61
        super(TokenController, self).__init__(*args, **kwargs)
62
63
        if cfg.CONF.auth.mode == 'standalone':
64
            self._auth_backend = get_backend_instance(name=cfg.CONF.auth.backend)
65
        else:
66
            self._auth_backend = None
67
68
    @jsexpose(body_cls=TokenAPI, status_code=http_client.CREATED)
69
    def post(self, request, **kwargs):
70
        if cfg.CONF.auth.mode == 'proxy':
71
            return self._handle_proxy_auth(request=request, **kwargs)
72
        elif cfg.CONF.auth.mode == 'standalone':
73
            return self._handle_standalone_auth(request=request, **kwargs)
74
75
    def _handle_proxy_auth(self, request, **kwargs):
76
        remote_addr = pecan.request.headers.get('x-forwarded-for', pecan.request.remote_addr)
77
        extra = {'remote_addr': remote_addr}
78
79
        if pecan.request.remote_user:
80
            ttl = getattr(request, 'ttl', None)
81
            try:
82
                token = self._create_token_for_user(username=pecan.request.remote_user, ttl=ttl)
83
            except TTLTooLargeException as e:
84
                self._abort_request(status_code=http_client.BAD_REQUEST,
85
                                    message=e.message)
86
            return self._process_successful_response(token=token)
87
88
        LOG.audit('Access denied to anonymous user.', extra=extra)
89
        self._abort_request()
90
91
    def _handle_standalone_auth(self, request, **kwargs):
92
        authorization = pecan.request.authorization
93
94
        auth_backend = self._auth_backend.__class__.__name__
95
        remote_addr = pecan.request.remote_addr
96
        extra = {'auth_backend': auth_backend, 'remote_addr': remote_addr}
97
98
        if not authorization:
99
            LOG.audit('Authorization header not provided', extra=extra)
100
            self._abort_request()
101
            return
102
103
        auth_type, auth_value = authorization
104
        if auth_type.lower() not in ['basic']:
105
            extra['auth_type'] = auth_type
106
            LOG.audit('Unsupported authorization type: %s' % (auth_type), extra=extra)
107
            self._abort_request()
108
            return
109
110
        try:
111
            auth_value = base64.b64decode(auth_value)
112
        except Exception:
113
            LOG.audit('Invalid authorization header', extra=extra)
114
            self._abort_request()
115
            return
116
117
        split = auth_value.split(':')
118
        if len(split) != 2:
119
            LOG.audit('Invalid authorization header', extra=extra)
120
            self._abort_request()
121
            return
122
123
        username, password = split
124
        result = self._auth_backend
125
126
        result = self._auth_backend.authenticate(username=username, password=password)
127
        if result is True:
128
            ttl = getattr(request, 'ttl', None)
129
            impersonate_user = getattr(request, 'user', None)
130
131
            if impersonate_user is not None:
132
                username = impersonate_user
133
            else:
134
                impersonate_user = getattr(request, 'impersonate_user', None)
135
                nickname_origin = getattr(request, 'nickname_origin', None)
136
            if impersonate_user is not None:
137
                try:
138
                    username = User.get_by_nickname(impersonate_user, nickname_origin).username
139
                except UserNotFoundError:
140
                    message = "Could not locate user %s@%s" % \
141
                              (impersonate_user, nickname_origin)
142
                    self._abort_request(status_code=http_client.BAD_REQUEST,
143
                                        message=message)
144
                    return
145
                except NoNicknameOriginProvidedError:
146
                    message = "Nickname origin is not provided for nickname '%s'" % \
147
                              impersonate_user
148
                    self._abort_request(status_code=http_client.BAD_REQUEST,
149
                                        message=message)
150
                    return
151
                except AmbiguousUserError:
152
                    message = "%s@%s matched more than one username" % \
153
                              (impersonate_user, nickname_origin)
154
                    self._abort_request(status_code=http_client.BAD_REQUEST,
155
                                        message=message)
156
                    return
157
            try:
158
                token = self._create_token_for_user(username=username, ttl=ttl)
159
                return self._process_successful_response(token=token)
160
            except TTLTooLargeException as e:
161
                self._abort_request(status_code=http_client.BAD_REQUEST,
162
                                    message=e.message)
163
                return
164
165
        LOG.audit('Invalid credentials provided', extra=extra)
166
        self._abort_request()
167
168
    def _abort_request(self, status_code=http_client.UNAUTHORIZED,
169
                       message='Invalid or missing credentials'):
170
        pecan.abort(status_code, message)
171
172
    def _process_successful_response(self, token):
173
        api_url = cfg.CONF.auth.api_url
174
        pecan.response.headers['X-API-URL'] = api_url
175
        return token
176
177
    def _create_token_for_user(self, username, ttl=None):
178
        tokendb = create_token(username=username, ttl=ttl)
179
        return TokenAPI.from_model(tokendb)
180