Completed
Pull Request — master (#2744)
by W
10:48 queued 04:22
created

TokenController   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 112
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 112
rs 10
c 0
b 0
f 0
wmc 23

8 Methods

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