Completed
Push — master ( bb5487...4fc1d0 )
by Manas
13:01 queued 05:18
created

_create_token_for_user()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
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 TTLTooLargeException
24
from st2common.models.api.base import jsexpose
25
from st2common.models.api.auth import TokenAPI
26
from st2common.services.access import create_token
27
from st2common import log as logging
28
from st2auth.backends import get_backend_instance
29
30
31
LOG = logging.getLogger(__name__)
32
33
34
class TokenController(rest.RestController):
35
    def __init__(self, *args, **kwargs):
36
        super(TokenController, self).__init__(*args, **kwargs)
37
38
        if cfg.CONF.auth.mode == 'standalone':
39
            self._auth_backend = get_backend_instance(name=cfg.CONF.auth.backend)
40
        else:
41
            self._auth_backend = None
42
43
    @jsexpose(body_cls=TokenAPI, status_code=http_client.CREATED)
44
    def post(self, request, **kwargs):
45
        if cfg.CONF.auth.mode == 'proxy':
46
            return self._handle_proxy_auth(request=request, **kwargs)
47
        elif cfg.CONF.auth.mode == 'standalone':
48
            return self._handle_standalone_auth(request=request, **kwargs)
49
50
    def _handle_proxy_auth(self, request, **kwargs):
51
        remote_addr = pecan.request.headers.get('x-forwarded-for', pecan.request.remote_addr)
52
        extra = {'remote_addr': remote_addr}
53
54
        if pecan.request.remote_user:
55
            ttl = getattr(request, 'ttl', None)
56
            try:
57
                token = self._create_token_for_user(username=pecan.request.remote_user, ttl=ttl)
58
            except TTLTooLargeException as e:
59
                self._abort_request(status_code=http_client.BAD_REQUEST,
60
                                    message=e.message)
61
            return self._process_successful_response(token=token)
62
63
        LOG.audit('Access denied to anonymous user.', extra=extra)
64
        self._abort_request()
65
66
    def _handle_standalone_auth(self, request, **kwargs):
67
        authorization = pecan.request.authorization
68
69
        auth_backend = self._auth_backend.__class__.__name__
70
        remote_addr = pecan.request.remote_addr
71
        extra = {'auth_backend': auth_backend, 'remote_addr': remote_addr}
72
73
        if not authorization:
74
            LOG.audit('Authorization header not provided', extra=extra)
75
            self._abort_request()
76
            return
77
78
        auth_type, auth_value = authorization
79
        if auth_type.lower() not in ['basic']:
80
            extra['auth_type'] = auth_type
81
            LOG.audit('Unsupported authorization type: %s' % (auth_type), extra=extra)
82
            self._abort_request()
83
            return
84
85
        try:
86
            auth_value = base64.b64decode(auth_value)
87
        except Exception:
88
            LOG.audit('Invalid authorization header', extra=extra)
89
            self._abort_request()
90
            return
91
92
        split = auth_value.split(':')
93
        if len(split) != 2:
94
            LOG.audit('Invalid authorization header', extra=extra)
95
            self._abort_request()
96
            return
97
98
        username, password = split
99
        result = self._auth_backend
100
101
        result = self._auth_backend.authenticate(username=username, password=password)
102
        if result is True:
103
            ttl = getattr(request, 'ttl', None)
104
            try:
105
                token = self._create_token_for_user(username=username, ttl=ttl)
106
                return self._process_successful_response(token=token)
107
            except TTLTooLargeException as e:
108
                self._abort_request(status_code=http_client.BAD_REQUEST,
109
                                    message=e.message)
110
                return
111
112
        LOG.audit('Invalid credentials provided', extra=extra)
113
        self._abort_request()
114
115
    def _abort_request(self, status_code=http_client.UNAUTHORIZED,
116
                       message='Invalid or missing credentials'):
117
        pecan.abort(status_code, message)
118
119
    def _process_successful_response(self, token):
120
        api_url = cfg.CONF.auth.api_url
121
        pecan.response.headers['X-API-URL'] = api_url
122
        return token
123
124
    def _create_token_for_user(self, username, ttl=None):
125
        tokendb = create_token(username=username, ttl=ttl)
126
        return TokenAPI.from_model(tokendb)
127