Passed
Push — master ( a6ab5f...fdd83f )
by Dean
02:55
created

OpenSSL.on_success()   B

Complexity

Conditions 6

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 9.7518

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 24
ccs 9
cts 17
cp 0.5294
rs 7.6129
c 1
b 0
f 0
cc 6
crap 9.7518
1 1
from plugin.core.libraries.tests.core.base import BaseTest
2
3 1
import logging
4
5 1
log = logging.getLogger(__name__)
6
7
8 1
class OpenSSL(BaseTest):
9 1
    name = 'openssl'
10 1
    optional = True
11
12 1
    @classmethod
13
    def test_import(cls):
14
        standard_version = cls._standard_version()
15
        standard_contexts = cls._standard_has_contexts()
16
        standard_sslwrap = cls._standard_has_sslwrap()
17
18
        bundled_version = cls._bundled_version()
19
20
        libraries = {
21
            'standard': {
22
                'version': standard_version,
23
                'contexts': standard_contexts,
24
                'sslwrap': standard_sslwrap
25
            },
26
27
            'bundled': {
28
                'version': bundled_version
29
            }
30
        }
31
32
        # Check if we should use the standard ssl library
33
        if cls._use_standard(libraries):
34
            return {
35
                'type': 'standard',
36
                'libraries': libraries,
37
38
                'versions': {
39
                    'openssl': standard_version
40
                }
41
            }
42
43
        # Test pyOpenSSL availability
44
        import OpenSSL.SSL
0 ignored issues
show
Comprehensibility Bug introduced by
OpenSSL is re-defining a name which is already available in the outer-scope (previously defined on line 8).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
45
46
        # Try construct SSL context
47
        ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
48
49
        # Ensure library has SNI support
50
        cnx = OpenSSL.SSL.Connection(ctx)
51
52
        if not hasattr(cnx, 'set_tlsext_host_name'):
53
            raise Exception('Missing SNI extension')
54
55
        # Ensure binding can be imported
56
        from cryptography.hazmat.bindings.openssl.binding import Binding
57
        assert Binding
58
59
        # Ensure secure connections work with requests
60
        from requests.packages.urllib3.contrib.pyopenssl import inject_into_urllib3
61
        import requests
62
63
        inject_into_urllib3()
64
65
        try:
66
            requests.head('https://api-v2launch.trakt.tv', timeout=3)
67
        except requests.RequestException as ex:
68
            # Ignore failed requests (server error, network problem, etc..)
69
            log.warn('Request failed: %s', ex, exc_info=True)
70
71
        return {
72
            'type': 'bundled',
73
            'libraries': libraries,
74
75
            'versions': {
76
                'openssl': bundled_version,
77
                'pyopenssl': getattr(OpenSSL, '__version__', None)
78
            }
79
        }
80
81 1
    @classmethod
82
    def on_success(cls, metadata):
83 1
        libraries = metadata['libraries']
84
85 1
        if not libraries['standard']['contexts']:
86 1
            log.debug('Standard SSL library doesn\'t support "SSLContext"')
87
        elif not libraries['standard']['sslwrap']:
88
            log.debug('Standard SSL library doesn\'t support "sslwrap"')
89
        elif libraries['bundled']['version'] > libraries['standard']['version']:
90
            log.debug('Standard SSL library is out of date')
91
92
        # Initialize ssl library
93 1
        if metadata['type'] == 'bundled':
94
            # Inject pyOpenSSL into requests
95 1
            log.debug('Using bundled SSL library (pyOpenSSL)')
96
97 1
            try:
98 1
                from requests.packages.urllib3.contrib.pyopenssl import inject_into_urllib3
99 1
                inject_into_urllib3()
100
            except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
101
                log.warn('Unable to inject pyOpenSSL into urllib3 - %s', ex, exc_info=True)
102
                return
103
        else:
104
            log.debug('Using standard SSL library (ssl)')
105
106
    #
107
    # Helpers
108
    #
109
110 1
    @classmethod
111
    def _use_standard(cls, libraries):
112
        if not libraries['standard']['contexts']:
113
            return False
114
115
        if not libraries['standard']['sslwrap']:
116
            return False
117
118
        # Ensure bundled library is available
119
        if libraries['bundled']['version'] is None:
120
            return True
121
122
        # Compare standard library versions
123
        if libraries['standard']['version'] is None:
124
            return False
125
126
        return libraries['standard']['version'] > libraries['bundled']['version']
127
128 1
    @classmethod
129
    def _standard_has_contexts(cls):
130
        try:
131
            import ssl
132
            import _ssl
133
134
            return hasattr(ssl, 'SSLContext') and hasattr(_ssl, '_SSLContext')
135
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
136
            log.warn('Unable to check if the standard ssl library supports "SSLContext": %s', ex, exc_info=True)
137
138
        return None
139
140 1
    @classmethod
141
    def _standard_has_sslwrap(cls):
142
        try:
143
            import _ssl
144
145
            return hasattr(_ssl, 'sslwrap') or hasattr(_ssl._SSLContext, '_wrap_socket')
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _SSLContext was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
146
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
147
            log.warn('Unable to check if the standard ssl library supports "sslwrap": %s', ex, exc_info=True)
148
149
        return None
150
151 1
    @classmethod
152
    def _standard_version(cls):
153
        try:
154
            import ssl
155
            return ssl.OPENSSL_VERSION_NUMBER
156
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
157
            log.warn('Unable to retrieve standard ssl library version: %s', ex, exc_info=True)
158
159
        return None
160
161 1
    @classmethod
162
    def _bundled_version(cls):
163
        try:
164
            from cryptography.hazmat.bindings.openssl.binding import Binding
165
            return Binding.lib.SSLeay()
166
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
167
            log.warn('Unable to retrieve bundled ssl library version: %s', ex, exc_info=True)
168
169
        return None
170