Passed
Push — master ( 6e8656...9792e6 )
by Dean
05:08 queued 02:39
created

OpenSSL._use_standard()   B

Complexity

Conditions 5

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 23.225

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 17
ccs 1
cts 10
cp 0.1
rs 8.5454
c 1
b 0
f 0
cc 5
crap 23.225
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
0 ignored issues
show
Configuration introduced by
The import cryptography.hazmat.bindings.openssl.binding could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
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
        # Enable secure error reporting
107 1
        from plugin.core.logger.handlers.error_reporter import ErrorReporter
108 1
        ErrorReporter.set_protocol('threaded+requests+https')
109
110
    #
111
    # Helpers
112
    #
113
114 1
    @classmethod
115
    def _use_standard(cls, libraries):
116
        if not libraries['standard']['contexts']:
117
            return False
118
119
        if not libraries['standard']['sslwrap']:
120
            return False
121
122
        # Ensure bundled library is available
123
        if libraries['bundled']['version'] is None:
124
            return True
125
126
        # Compare standard library versions
127
        if libraries['standard']['version'] is None:
128
            return False
129
130
        return libraries['standard']['version'] > libraries['bundled']['version']
131
132 1
    @classmethod
133
    def _standard_has_contexts(cls):
134
        try:
135
            import ssl
136
            import _ssl
137
138
            return hasattr(ssl, 'SSLContext') and hasattr(_ssl, '_SSLContext')
139
        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...
140
            log.warn('Unable to check if the standard ssl library supports "SSLContext": %s', ex, exc_info=True)
141
142
        return None
143
144 1
    @classmethod
145
    def _standard_has_sslwrap(cls):
146
        try:
147
            import _ssl
148
149
            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...
150
        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...
151
            log.warn('Unable to check if the standard ssl library supports "sslwrap": %s', ex, exc_info=True)
152
153
        return None
154
155 1
    @classmethod
156
    def _standard_version(cls):
157
        try:
158
            import ssl
159
            return ssl.OPENSSL_VERSION_NUMBER
160
        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...
161
            log.warn('Unable to retrieve standard ssl library version: %s', ex, exc_info=True)
162
163
        return None
164
165 1
    @classmethod
166
    def _bundled_version(cls):
167
        try:
168
            from cryptography.hazmat.bindings.openssl.binding import Binding
0 ignored issues
show
Configuration introduced by
The import cryptography.hazmat.bindings.openssl.binding could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
169
            return Binding.lib.SSLeay()
170
        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...
171
            log.warn('Unable to retrieve bundled ssl library version: %s', ex, exc_info=True)
172
173
        return None
174