· 7 years ago · Sep 14, 2018, 08:18 PM
1import config
2import jwt
3import logging
4import sys
5import uuid
6
7from datetime import datetime, timedelta
8from flask import request, make_response, redirect, url_for
9from requests_oauthlib import OAuth2Session
10
11client_id = config.client_id
12client_secret = config.client_secret
13authorization_endpoint = config.authorization_endpoint
14token_endpoint = config.token_endpoint
15userinfo_endpoint = config.userinfo_endpoint
16redirect_uri = config.redirect_uri
17base_uri = config.base_uri
18jwt_secrets = config.jwt_secrets.split(',')
19algorithm = 'HS256'
20jwt_cookie_key = 'jwt_token'
21oauth_token = 'oauth_token'
22oauth_state = 'oauth_state'
23oauth_token_key = base_uri + '/' + oauth_token
24oauth_state_key = base_uri + '/' + oauth_state
25oauth_initial_path_key = base_uri + '/initial_path'
26log = logging.getLogger('oauthlib')
27scope = config.scope
28jwt_keys = [
29 "jti",
30 "iss",
31 "aud",
32 "exp",
33 "iat",
34 "nbf",
35 oauth_token_key,
36 oauth_state_key
37]
38
39log.addHandler(logging.StreamHandler(sys.stdout))
40log.setLevel(logging.DEBUG)
41
42
43class Cookie:
44 def __init__(self):
45 self.keys = {}
46 self.ages = {}
47 self.secures = {}
48 self.deletes = []
49
50 def remove_jwt(self, key):
51 self.deletes.append(key)
52
53 def add_jwt(self, key, jwt, max_age=300, secure=True):
54 self.keys[key] = jwt
55 self.ages[key] = max_age
56 self.secures[key] = secure
57
58 def populate_resp(self, resp):
59 for key, token in self.keys.items():
60 resp.set_cookie(
61 key = key,
62 max_age = self.ages[key],
63 secure = self.secures[key],
64 value = jwt.encode(
65 token,
66
67 # The first secret is the current signer.
68 jwt_secrets[0],
69 algorithm = algorithm
70 )
71 )
72 for key in self.deletes:
73 resp.set_cookie(
74 key=key,
75 max_age=0
76 )
77 return resp
78
79
80def get_jwt_token(cookies, key):
81 """
82 Retrieve the jwt token's as a key, value object
83 from the flask request cookies object.
84 If no token exists, returns an empty object.
85 """
86 if key in request.cookies:
87
88 # We take multiple secrets to allow for online secret rotation.
89 # The first secret is the current signer,
90 # and the others are potentially still in use, but will
91 # be rotated out.
92 for jwt_secret in jwt_secrets:
93 try:
94 # github.com/jpadilla/pyjwt/blob/master/tests/test_api_jwt.py
95 # This will validate:
96 # iss, aud
97 # This will validate (if they are in the request):
98 # exp, nbf, iat
99 return jwt.decode(
100 request.cookies[key],
101 jwt_secret,
102 issuer=base_uri,
103 audience=base_uri,
104 algorithms=[algorithm]
105 )
106 except jwt.exceptions.InvalidTokenError as e:
107 # will catch exp, nbf, iat, iss, aud errors
108 print(e)
109 continue
110
111 return {}
112
113
114def jwt_params(
115 oauth_token = None,
116 oauth_state = None,
117 sub = None,
118 iss = base_uri,
119 aud = base_uri,
120 exp_seconds = 300,
121 **rest):
122 now = datetime.utcnow()
123 expires = now + timedelta(seconds=exp_seconds)
124 token = rest
125 if oauth_token:
126 token[oauth_token_key] = oauth_token
127 if oauth_state:
128 token[oauth_state_key] = oauth_state
129 if sub:
130 token['sub'] = sub
131 token['jti'] = str(uuid.uuid1())
132 token['iss'] = base_uri
133 token['aud'] = base_uri
134 token['exp'] = expires
135 token['iat'] = now
136 token['nbf'] = now
137 return token
138
139
140def authenticate():
141 """
142 Run the given request handler, passing it the user.
143 If the user has no valid auth token, then
144 redirect the user/resource owner to the keymaster
145 without invoking the given request handler.
146 """
147 jwt = get_jwt_token(request.cookies, oauth_token)
148 if oauth_token_key in jwt:
149 return None
150
151 client = OAuth2Session(
152 client_id,
153 scope = scope,
154 redirect_uri = redirect_uri
155 )
156 authorization_url, state = client.authorization_url(authorization_endpoint)
157
158 # State is used to prevent CSRF, keep this for later.
159 cookie = Cookie()
160 print(request.full_path)
161 state_token_params = jwt_params(
162 oauth_state=state,
163 initial_path=request.full_path
164 )
165 cookie.add_jwt(oauth_state, state_token_params)
166 return cookie.populate_resp(make_response(redirect(authorization_url)))
167
168
169def oauth2_callback():
170 print('callbackworks?')
171 """
172 The user has been redirected back from keymaster to your registered
173 callback URL. With this redirection comes an authorization code included
174 in the redirect URL. We will use that to obtain an access token.
175 """
176 jwt = get_jwt_token(request.cookies, oauth_state)
177 if oauth_state_key not in jwt:
178 # something is wrong with the state token,
179 # so redirect back to start over.
180 return redirect(url_for('/broken'))
181 initial_path = url_for('/leaderboard/creator-vp')
182 if oauth_initial_path_key in jwt:
183 initial_path = jwt[oauth_initial_path_key]
184 client = OAuth2Session(
185 client_id,
186 state=jwt[oauth_state_key],
187 redirect_uri=redirect_uri
188 )
189
190 token = client.fetch_token(
191 token_endpoint,
192 client_secret=client_secret,
193 authorization_response=request.url
194 )
195 userinfo = client.get(userinfo_endpoint).json()
196
197 # Save the token
198 cookie = Cookie()
199 exp_seconds = 60 * 60 * 4
200 if "ExpiresIn" in token:
201 exp_seconds = token['ExpiresIn']
202 jwt_token_params = jwt_params(
203 exp_seconds=exp_seconds,
204 oauth_token=token,
205 **userinfo
206 )
207 cookie.add_jwt(oauth_token, jwt_token_params)
208 cookie.remove_jwt(oauth_state)
209 print('initial path: %s' % initial_path)
210 return cookie.populate_resp(make_response(redirect(initial_path)))