Passed
Push — master ( aeb165...d9ae97 )
by Dean
03:04
created

ApiManager.get_service()   A

Complexity

Conditions 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12
Metric Value
cc 3
dl 0
loc 14
ccs 0
cts 7
cp 0
crap 12
rs 9.4285
1
from plugin.api.core.exceptions import ApiError
2
from plugin.preferences import Preferences
3
4
from threading import Lock
5
import logging
6
7
log = logging.getLogger(__name__)
8
9
10
class ApiContext(object):
11
    def __init__(self, method, headers, body):
12
        self.method = method
13
        self.headers = headers
14
        self.body = body
15
16
        # Validate token
17
        self.token = self._validate_token()
18
19
    def _validate_token(self):
20
        token = self.headers.get('X-Channel-Token') if self.headers else None
21
22
        if not token:
23
            return None
24
25
        # Validate `token`
26
        system = ApiManager.get_service('system')
27
28
        try:
29
            return system.validate(token)
30
        except:
31
            return None
32
33
34
class ApiManager(object):
35
    service_classes = {}
36
    services = {}
37
38
    # Call attributes
39
    lock = Lock()
40
    context = None
41
42
    @classmethod
43
    def process(cls, method, headers, body, key, *args, **kwargs):
44
        log.debug('Handling API %s request %r - args: %r, kwargs: %r', method, key, args, kwargs)
45
46
        if not Preferences.get('api.enabled'):
47
            log.debug('Unable to process request, API is currently disabled')
48
            return cls.build_error('disabled', 'Unable to process request, API is currently disabled')
49
50
        k_service, k_method = key.rsplit('.', 1)
51
52
        # Try find matching service
53
        service = cls.get_service(k_service)
54
55
        if service is None:
56
            log.warn('Unable to find service: %r', k_service)
57
            return cls.build_error('unknown.service', 'Unable to find service: %r' % k_service)
58
59
        func = getattr(service, k_method, None)
60
61
        if func is None:
62
            log.warn('Unable to find method: %r', k_method)
63
            return cls.build_error('unknown.method', 'Unable to find method: %r' % k_method)
64
65
        # Validate
66
        meta = getattr(func, '__meta__', {})
67
68
        if not meta.get('exposed', False):
69
            log.warn('Method is not exposed: %r', k_method)
70
            return cls.build_error('restricted.method', 'Method is not exposed: %r' % k_method)
71
72
        # TODO validate authentication
73
74
        # Execute request handler
75
        try:
76
            result = cls.call(method, headers, body, func, args, kwargs)
77
        except ApiError, ex:
78
            log.warn('Error returned while handling request %r: %r', key, ex, exc_info=True)
79
            return cls.build_error('error.%s' % ex.code, ex.message)
80
        except Exception, ex:
81
            log.error('Exception raised while handling request %r: %s', key, ex, exc_info=True)
82
            return cls.build_error('exception', 'Exception raised while handling the request')
83
84
        # Build response
85
        return cls.build_response(result)
86
87
    @classmethod
88
    def call(cls, method, headers, body, func, args, kwargs):
89
        with cls.lock:
90
            # Construct context
91
            cls.context = ApiContext(method, headers, body)
92
93
            # Call function
94
            return func(*args, **kwargs)
95
96
    @classmethod
97
    def register(cls, service):
98
        key = service.__key__
99
100
        if not key:
101
            log.warn('Service %r has an invalid "__key__" attribute', service)
102
            return
103
104
        cls.service_classes[key] = service
105
106
        log.debug('Registered service: %r (%r)', key, service)
107
108
    @classmethod
109
    def get_service(cls, key):
110
        if key in cls.services:
111
            # Service already constructed
112
            return cls.services[key]
113
114
        if key not in cls.service_classes:
115
            # Service doesn't exist
116
            return None
117
118
        # Construct service
119
        cls.services[key] = cls.service_classes[key](cls)
120
121
        return cls.services[key]
122
123
    @classmethod
124
    def build_error(cls, code, message=None):
125
        result = {
126
            'error': {
127
                'code': code
128
            }
129
        }
130
131
        if message:
132
            result['error']['message'] = message
133
134
        return result
135
136
    @classmethod
137
    def build_response(cls, result):
138
        return {
139
            'result': result
140
        }
141