-
Notifications
You must be signed in to change notification settings - Fork 407
/
Copy pathdecorators.py
145 lines (103 loc) · 4.01 KB
/
decorators.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""This module provides decorators to the rest of the library."""
import os
from functools import wraps
from io import BytesIO as StringIO
from requests.models import Response
class RequestsStringIO(StringIO):
"""Shim compatibility for string IO."""
def read(self, n=-1, *args, **kwargs):
"""Ignore extra args and kwargs."""
# StringIO is an old-style class, so can't use super
return StringIO.read(self, n)
def requires_auth(func):
"""Decorator to note which object methods require authorization."""
@wraps(func)
def auth_wrapper(self, *args, **kwargs):
if hasattr(self, "session") and self.session.has_auth():
return func(self, *args, **kwargs)
else:
from .exceptions import error_for
# Mock a 401 response
r = generate_fake_error_response(
'{"message": "Requires authentication"}'
)
raise error_for(r)
return auth_wrapper
def requires_basic_auth(func):
"""Specific (basic) authentication decorator.
This is used to note which object methods require username/password
authorization and won't work with token based authorization.
"""
@wraps(func)
def auth_wrapper(self, *args, **kwargs):
if hasattr(self, "session") and self.session.auth:
return func(self, *args, **kwargs)
else:
from .exceptions import error_for
# Mock a 401 response
r = generate_fake_error_response(
'{"message": "Requires username/password authentication"}'
)
raise error_for(r)
return auth_wrapper
def requires_app_credentials(func):
"""Require client_id and client_secret to be associated.
This is used to note and enforce which methods require a client_id and
client_secret to be used.
"""
@wraps(func)
def auth_wrapper(self, *args, **kwargs):
client_id, client_secret = self.session.retrieve_client_credentials()
if client_id and client_secret:
return func(self, *args, **kwargs)
else:
from .exceptions import error_for
# Mock a 401 response
r = generate_fake_error_response(
'{"message": "Requires username/password authentication"}'
)
raise error_for(r)
return auth_wrapper
def requires_app_bearer_auth(func):
"""Require the use of application authentication.
.. versionadded:: 1.2.0
"""
@wraps(func)
def auth_wrapper(self, *args, **kwargs):
from . import session
if isinstance(self.session.auth, session.AppBearerTokenAuth):
return func(self, *args, **kwargs)
else:
from . import exceptions
raise exceptions.MissingAppBearerAuthentication(
"This method requires GitHub App authentication."
)
return auth_wrapper
def requires_app_installation_auth(func):
"""Require the use of App's installation authentication.
.. versionadded:: 1.2.0
"""
@wraps(func)
def auth_wrapper(self, *args, **kwargs):
from . import session
if isinstance(self.session.auth, session.AppInstallationTokenAuth):
return func(self, *args, **kwargs)
else:
from . import exceptions
raise exceptions.MissingAppInstallationAuthentication(
"This method requires GitHub App authentication."
)
return auth_wrapper
def generate_fake_error_response(msg, status_code=401, encoding="utf-8"):
"""Generate a fake Response from requests."""
r = Response()
r.status_code = status_code
r.encoding = encoding
r.raw = RequestsStringIO(msg.encode())
r._content_consumed = True
r._content = r.raw.read()
return r
# Use mock decorators when generating documentation, so all functino signatures
# are displayed correctly
if os.getenv("GENERATING_DOCUMENTATION", None) == "github3":
requires_auth = requires_basic_auth = lambda x: x # noqa # (No coverage)