Completed
Pull Request — master (#2744)
by W
06:15
created

TokenController.get_one()   B

Complexity

Conditions 5

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
dl 0
loc 19
rs 8.5454
c 0
b 0
f 0
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
            print e.message
70
            self._abort_request(status_code=http_client.INTERNAL_SERVER_ERROR, message='')
71
72
    def _handle_proxy_auth(self, request, **kwargs):
73
        remote_addr = pecan.request.headers.get('x-forwarded-for', pecan.request.remote_addr)
74
        extra = {'remote_addr': remote_addr}
75
76
        if pecan.request.remote_user:
77
            ttl = getattr(request, 'ttl', None)
78
            try:
79
                token = self._create_token_for_user(username=pecan.request.remote_user, ttl=ttl)
80
            except TTLTooLargeException as e:
81
                self._abort_request(status_code=http_client.BAD_REQUEST,
82
                                    message=e.message)
83
            return self._process_successful_response(token=token)
84
85
        LOG.audit('Access denied to anonymous user.', extra=extra)
86
        self._abort_request()
87
88
    def _handle_standalone_auth(self, request, **kwargs):
89
        authorization = pecan.request.authorization
90
91
        auth_backend = self._auth_backend.__class__.__name__
92
        remote_addr = pecan.request.remote_addr
93
        extra = {'auth_backend': auth_backend, 'remote_addr': remote_addr}
94
95
        if not authorization:
96
            LOG.audit('Authorization header not provided', extra=extra)
97
            self._abort_request()
98
            return
99
100
        auth_type, auth_value = authorization
101
        if auth_type.lower() not in ['basic']:
102
            extra['auth_type'] = auth_type
103
            LOG.audit('Unsupported authorization type: %s' % (auth_type), extra=extra)
104
            self._abort_request()
105
            return
106
107
        try:
108
            auth_value = base64.b64decode(auth_value)
109
        except Exception:
110
            LOG.audit('Invalid authorization header', extra=extra)
111
            self._abort_request()
112
            return
113
114
        split = auth_value.split(':')
115
        if len(split) != 2:
116
            LOG.audit('Invalid authorization header', extra=extra)
117
            self._abort_request()
118
            return
119
120
        username, password = split
121
        result = self._auth_backend
122
123
        result = self._auth_backend.authenticate(username=username, password=password)
124
        if result is True:
125
            ttl = getattr(request, 'ttl', None)
126
            try:
127
                token = self._create_token_for_user(username=username, ttl=ttl)
128
                return self._process_successful_response(token=token)
129
            except TTLTooLargeException as e:
130
                self._abort_request(status_code=http_client.BAD_REQUEST,
131
                                    message=e.message)
132
                return
133
134
        LOG.audit('Invalid credentials provided', extra=extra)
135
        self._abort_request()
136
137
    def _abort_request(self, status_code=http_client.UNAUTHORIZED,
138
                       message='Invalid or missing credentials'):
139
        pecan.abort(status_code, message)
140
141
    def _process_successful_response(self, token):
142
        api_url = cfg.CONF.auth.api_url
143
        pecan.response.headers['X-API-URL'] = api_url
144
        return token
145
146
    def _create_token_for_user(self, username, ttl=None):
147
        tokendb = create_token(username=username, ttl=ttl)
148
        return TokenAPI.from_model(tokendb)
149