| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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) |
| OLD | NEW |