Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/rest_framework/authentication.py: 31%

108 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2Provides various authentication policies. 

3""" 

4import base64 

5import binascii 

6 

7from django.contrib.auth import authenticate, get_user_model 

8from django.middleware.csrf import CsrfViewMiddleware 

9from django.utils.translation import gettext_lazy as _ 

10 

11from rest_framework import HTTP_HEADER_ENCODING, exceptions 

12 

13 

14def get_authorization_header(request): 

15 """ 

16 Return request's 'Authorization:' header, as a bytestring. 

17 

18 Hide some test client ickyness where the header can be unicode. 

19 """ 

20 auth = request.META.get('HTTP_AUTHORIZATION', b'') 

21 if isinstance(auth, str): 

22 # Work around django test client oddness 

23 auth = auth.encode(HTTP_HEADER_ENCODING) 

24 return auth 

25 

26 

27class CSRFCheck(CsrfViewMiddleware): 

28 def _reject(self, request, reason): 

29 # Return the failure reason instead of an HttpResponse 

30 return reason 

31 

32 

33class BaseAuthentication: 

34 """ 

35 All authentication classes should extend BaseAuthentication. 

36 """ 

37 

38 def authenticate(self, request): 

39 """ 

40 Authenticate the request and return a two-tuple of (user, token). 

41 """ 

42 raise NotImplementedError(".authenticate() must be overridden.") 

43 

44 def authenticate_header(self, request): 

45 """ 

46 Return a string to be used as the value of the `WWW-Authenticate` 

47 header in a `401 Unauthenticated` response, or `None` if the 

48 authentication scheme should return `403 Permission Denied` responses. 

49 """ 

50 pass 

51 

52 

53class BasicAuthentication(BaseAuthentication): 

54 """ 

55 HTTP Basic authentication against username/password. 

56 """ 

57 www_authenticate_realm = 'api' 

58 

59 def authenticate(self, request): 

60 """ 

61 Returns a `User` if a correct username and password have been supplied 

62 using HTTP Basic authentication. Otherwise returns `None`. 

63 """ 

64 auth = get_authorization_header(request).split() 

65 

66 if not auth or auth[0].lower() != b'basic': 

67 return None 

68 

69 if len(auth) == 1: 

70 msg = _('Invalid basic header. No credentials provided.') 

71 raise exceptions.AuthenticationFailed(msg) 

72 elif len(auth) > 2: 

73 msg = _('Invalid basic header. Credentials string should not contain spaces.') 

74 raise exceptions.AuthenticationFailed(msg) 

75 

76 try: 

77 try: 

78 auth_decoded = base64.b64decode(auth[1]).decode('utf-8') 

79 except UnicodeDecodeError: 

80 auth_decoded = base64.b64decode(auth[1]).decode('latin-1') 

81 auth_parts = auth_decoded.partition(':') 

82 except (TypeError, UnicodeDecodeError, binascii.Error): 

83 msg = _('Invalid basic header. Credentials not correctly base64 encoded.') 

84 raise exceptions.AuthenticationFailed(msg) 

85 

86 userid, password = auth_parts[0], auth_parts[2] 

87 return self.authenticate_credentials(userid, password, request) 

88 

89 def authenticate_credentials(self, userid, password, request=None): 

90 """ 

91 Authenticate the userid and password against username and password 

92 with optional request for context. 

93 """ 

94 credentials = { 

95 get_user_model().USERNAME_FIELD: userid, 

96 'password': password 

97 } 

98 user = authenticate(request=request, **credentials) 

99 

100 if user is None: 

101 raise exceptions.AuthenticationFailed(_('Invalid username/password.')) 

102 

103 if not user.is_active: 

104 raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) 

105 

106 return (user, None) 

107 

108 def authenticate_header(self, request): 

109 return 'Basic realm="%s"' % self.www_authenticate_realm 

110 

111 

112class SessionAuthentication(BaseAuthentication): 

113 """ 

114 Use Django's session framework for authentication. 

115 """ 

116 

117 def authenticate(self, request): 

118 """ 

119 Returns a `User` if the request session currently has a logged in user. 

120 Otherwise returns `None`. 

121 """ 

122 

123 # Get the session-based user from the underlying HttpRequest object 

124 user = getattr(request._request, 'user', None) 

125 

126 # Unauthenticated, CSRF validation not required 

127 if not user or not user.is_active: 127 ↛ 130line 127 didn't jump to line 130, because the condition on line 127 was never false

128 return None 

129 

130 self.enforce_csrf(request) 

131 

132 # CSRF passed with authenticated user 

133 return (user, None) 

134 

135 def enforce_csrf(self, request): 

136 """ 

137 Enforce CSRF validation for session based authentication. 

138 """ 

139 def dummy_get_response(request): # pragma: no cover 

140 return None 

141 

142 check = CSRFCheck(dummy_get_response) 

143 # populates request.META['CSRF_COOKIE'], which is used in process_view() 

144 check.process_request(request) 

145 reason = check.process_view(request, None, (), {}) 

146 if reason: 

147 # CSRF failed, bail with explicit error message 

148 raise exceptions.PermissionDenied('CSRF Failed: %s' % reason) 

149 

150 

151class TokenAuthentication(BaseAuthentication): 

152 """ 

153 Simple token based authentication. 

154 

155 Clients should authenticate by passing the token key in the "Authorization" 

156 HTTP header, prepended with the string "Token ". For example: 

157 

158 Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a 

159 """ 

160 

161 keyword = 'Token' 

162 model = None 

163 

164 def get_model(self): 

165 if self.model is not None: 

166 return self.model 

167 from rest_framework.authtoken.models import Token 

168 return Token 

169 

170 """ 

171 A custom token model may be used, but must have the following properties. 

172 

173 * key -- The string identifying the token 

174 * user -- The user to which the token belongs 

175 """ 

176 

177 def authenticate(self, request): 

178 auth = get_authorization_header(request).split() 

179 

180 if not auth or auth[0].lower() != self.keyword.lower().encode(): 

181 return None 

182 

183 if len(auth) == 1: 

184 msg = _('Invalid token header. No credentials provided.') 

185 raise exceptions.AuthenticationFailed(msg) 

186 elif len(auth) > 2: 

187 msg = _('Invalid token header. Token string should not contain spaces.') 

188 raise exceptions.AuthenticationFailed(msg) 

189 

190 try: 

191 token = auth[1].decode() 

192 except UnicodeError: 

193 msg = _('Invalid token header. Token string should not contain invalid characters.') 

194 raise exceptions.AuthenticationFailed(msg) 

195 

196 return self.authenticate_credentials(token) 

197 

198 def authenticate_credentials(self, key): 

199 model = self.get_model() 

200 try: 

201 token = model.objects.select_related('user').get(key=key) 

202 except model.DoesNotExist: 

203 raise exceptions.AuthenticationFailed(_('Invalid token.')) 

204 

205 if not token.user.is_active: 

206 raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) 

207 

208 return (token.user, token) 

209 

210 def authenticate_header(self, request): 

211 return self.keyword 

212 

213 

214class RemoteUserAuthentication(BaseAuthentication): 

215 """ 

216 REMOTE_USER authentication. 

217 

218 To use this, set up your web server to perform authentication, which will 

219 set the REMOTE_USER environment variable. You will need to have 

220 'django.contrib.auth.backends.RemoteUserBackend in your 

221 AUTHENTICATION_BACKENDS setting 

222 """ 

223 

224 # Name of request header to grab username from. This will be the key as 

225 # used in the request.META dictionary, i.e. the normalization of headers to 

226 # all uppercase and the addition of "HTTP_" prefix apply. 

227 header = "REMOTE_USER" 

228 

229 def authenticate(self, request): 

230 user = authenticate(request=request, remote_user=request.META.get(self.header)) 

231 if user and user.is_active: 

232 return (user, None)