Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(67)

Side by Side Diff: appengine/chromium_rietveld/codereview/auth_utils.py

Issue 2075803002: [Rietveld] Allow whitelisted email accounts to access Rietveld. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Support emails in all domains instead of just App Engine service accounts. Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | appengine/chromium_rietveld/codereview/views.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2013 Google Inc. 1 # Copyright 2013 Google Inc.
2 # 2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); 3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License. 4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at 5 # You may obtain a copy of the License at
6 # 6 #
7 # http://www.apache.org/licenses/LICENSE-2.0 7 # http://www.apache.org/licenses/LICENSE-2.0
8 # 8 #
9 # Unless required by applicable law or agreed to in writing, software 9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, 10 # distributed under the License is distributed on an "AS IS" BASIS,
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
45 45
46 46
47 EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' 47 EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
48 48
49 49
50 class SecretKey(ndb.Model): 50 class SecretKey(ndb.Model):
51 """Model for representing project secret keys.""" 51 """Model for representing project secret keys."""
52 client_id = ndb.StringProperty(required=True, indexed=False) 52 client_id = ndb.StringProperty(required=True, indexed=False)
53 client_secret = ndb.StringProperty(required=True, indexed=False) 53 client_secret = ndb.StringProperty(required=True, indexed=False)
54 additional_client_ids = ndb.StringProperty(repeated=True, indexed=False) 54 additional_client_ids = ndb.StringProperty(repeated=True, indexed=False)
55 whitelisted_emails = ndb.StringProperty(repeated=True, indexed=False)
55 56
56 GLOBAL_KEY = '_global_config' 57 GLOBAL_KEY = '_global_config'
57 58
58 @classmethod 59 @classmethod
59 def set_config(cls, client_id, client_secret, additional_client_ids=None): 60 def set_config(cls, client_id, client_secret, additional_client_ids=None,
61 whitelisted_emails=None):
60 """Sets global config object using a Client ID and Secret. 62 """Sets global config object using a Client ID and Secret.
61 63
62 Args: 64 Args:
63 client_id: String containing Google APIs Client ID. 65 client_id: String containing Google APIs Client ID.
64 client_secret: String containing Google APIs Client Secret. 66 client_secret: String containing Google APIs Client Secret.
65 additional_client_ids: List of strings for Google APIs Client IDs which 67 additional_client_ids: List of strings for Google APIs Client IDs which
66 are allowed against this application but not used to mint tokens on 68 are allowed against this application but not used to mint tokens on
67 the server. For example, service accounts which interact with this 69 the server. For example, service accounts which interact with this
68 application. Defaults to None. 70 application. Defaults to None.
71 whitelisted_emails: List of email addresses whitelisted for access
72 to this application.
69 73
70 Returns: 74 Returns:
71 The inserted SecretKey object. 75 The inserted SecretKey object.
72 """ 76 """
73 additional_client_ids = additional_client_ids or [] 77 additional_client_ids = additional_client_ids or []
78 whitelisted_emails = whitelisted_emails or []
74 config = cls(id=cls.GLOBAL_KEY, 79 config = cls(id=cls.GLOBAL_KEY,
75 client_id=client_id, client_secret=client_secret, 80 client_id=client_id, client_secret=client_secret,
76 additional_client_ids=additional_client_ids) 81 additional_client_ids=additional_client_ids,
82 whitelisted_emails=whitelisted_emails)
77 config.put() 83 config.put()
78 return config 84 return config
79 85
80 @classmethod 86 @classmethod
81 def get_config(cls): 87 def get_config(cls):
82 """Gets tuple of Client ID and Secret from global config object. 88 """Gets tuple of Client ID and Secret from global config object.
83 89
84 Returns: 90 Returns:
85 3-tuple containing the Client ID and Secret from the global config 91 4-tuple containing the Client ID and Secret from the global config
86 SecretKey object as well as a list of other allowed client IDs, if the 92 SecretKey object as well as a list of other allowed client IDs and
87 config is in the datastore, else the tuple (None, None, []). 93 a list of the whiltelisted emails, if the config is in the datastore,
94 else the tuple (None, None, [], []).
88 """ 95 """
89 config = cls.get_by_id(cls.GLOBAL_KEY) 96 config = cls.get_by_id(cls.GLOBAL_KEY)
90 if config is None: 97 if config is None:
91 return None, None, [] 98 return None, None, [], []
92 else: 99 else:
93 return (config.client_id, config.client_secret, 100 return (config.client_id, config.client_secret,
94 config.additional_client_ids) 101 config.additional_client_ids, config.whitelisted_emails)
95 102
96 103
97 def _get_client_id(tries=3): 104 def _get_client_id(tries=3):
98 """Call oauth.get_client_id() and retry if it times out.""" 105 """Call oauth.get_client_id() and retry if it times out."""
99 for attempt in xrange(tries): 106 for attempt in xrange(tries):
100 try: 107 try:
101 return oauth.get_client_id(EMAIL_SCOPE) 108 return oauth.get_client_id(EMAIL_SCOPE)
102 except apiproxy_errors.DeadlineExceededError: 109 except apiproxy_errors.DeadlineExceededError:
103 logging.error('get_client_id() timed out on attempt %r', attempt) 110 logging.error('get_client_id() timed out on attempt %r', attempt)
104 if attempt == tries - 1: 111 if attempt == tries - 1:
105 raise 112 raise
106 113
107 114
115 def _is_allowed_client_id():
116 """Returns True if the client id is allowed to reach this application."""
117 try:
118 current_client_id = _get_client_id()
119 except oauth.Error as e:
120 logging.debug('OAuth error occurred: %s' % str(e.__class__.__name__))
121 return False
122
123 # SecretKey.get_config() below returns client ids of service accounts that
124 # are authorized to connect to this instance.
125 # If modifying the following lines, please look for places where
126 # 'developer.gserviceaccount.com' (service account domain) is used. Some
127 # code relies on the filtering performed here.
128 accepted_client_id, _, additional_client_ids, _ = SecretKey.get_config()
129 if (accepted_client_id != current_client_id and
130 current_client_id not in additional_client_ids):
131 logging.debug('Client ID %r not intended for this application.',
132 current_client_id)
133 return False
134
135 return True
136
137
138 def _is_allowed_email():
139 """Returns True if the AppEngine service account is allowed."""
140 try:
141 oauth_user = oauth.get_current_user(EMAIL_SCOPE)
142 except oauth.Error as e:
143 logging.debug('OAuth error occurred: %s' % str(e.__class__.__name__))
144 return False
145
146 if not oauth_user:
147 return False
148
149 _, _, _, whitelisted_emails = SecretKey.get_config()
150 if oauth_user.email() not in whitelisted_emails:
151 logging.debug('Email account %r not intended for this application.',
152 oauth_user)
153 return False
154
155 return True
156
157
108 def get_current_rietveld_oauth_user(): 158 def get_current_rietveld_oauth_user():
109 """Gets the current OAuth 2.0 user associated with a request. 159 """Gets the current OAuth 2.0 user associated with a request.
110 160
111 This user must be intending to reach this application, so we check the token 161 This user must be intending to reach this application, so we check the token
112 info to verify this is the case. 162 info to verify this is the case.
113 163
114 Returns: 164 Returns:
115 A users.User object that was retrieved from the App Engine OAuth library if 165 A users.User object that was retrieved from the App Engine OAuth library if
116 the token is valid, otherwise None. 166 the token is valid, otherwise None.
117 """ 167 """
118 # TODO(dhermes): Address local environ here as well. 168 # TODO(dhermes): Address local environ here as well.
119 try: 169 if not _is_allowed_client_id() and not _is_allowed_email():
120 current_client_id = _get_client_id()
121 except oauth.Error as e:
122 logging.debug('OAuth error occurred: %s' % str(e.__class__.__name__))
123 return
124
125 # SecretKey.get_config() below returns client ids of service accounts that
126 # are authorized to connect to this instance.
127 # If modifying the following lines, please look for places where
128 # 'developer.gserviceaccount.com' (service account domain) is used. Some
129 # code relies on the filtering performed here.
130 accepted_client_id, _, additional_client_ids = SecretKey.get_config()
131 if (accepted_client_id != current_client_id and
132 current_client_id not in additional_client_ids):
133 logging.debug('Client ID %r not intended for this application.',
134 current_client_id)
135 return 170 return
136 171
137 try: 172 try:
138 return oauth.get_current_user(EMAIL_SCOPE) 173 return oauth.get_current_user(EMAIL_SCOPE)
139 except oauth.Error: 174 except oauth.Error:
140 logging.warning('A Client ID was retrieved with no corresponding user.') 175 logging.warning('A Client ID was retrieved with no corresponding user.')
141 176
142 177
143 def get_current_user(): 178 def get_current_user():
144 """Gets the current user associated with a request. 179 """Gets the current user associated with a request.
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
190 if cookie_user_is_admin: 225 if cookie_user_is_admin:
191 return cookie_user_is_admin 226 return cookie_user_is_admin
192 227
193 # oauth.is_current_user_admin is not sufficient, we must first check that the 228 # oauth.is_current_user_admin is not sufficient, we must first check that the
194 # OAuth 2.0 user has a token minted for this application. 229 # OAuth 2.0 user has a token minted for this application.
195 rietveld_user = get_current_rietveld_oauth_user() 230 rietveld_user = get_current_rietveld_oauth_user()
196 if rietveld_user is None: 231 if rietveld_user is None:
197 return False 232 return False
198 233
199 return oauth.is_current_user_admin(EMAIL_SCOPE) 234 return oauth.is_current_user_admin(EMAIL_SCOPE)
OLDNEW
« no previous file with comments | « no previous file | appengine/chromium_rietveld/codereview/views.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698