Passed
Push — develop ( e1e109...2dd18e )
by Plexxi
06:53 queued 03:26
created

TokenValidationController   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 17
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 10
c 0
b 0
f 0
wmc 4

1 Method

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