1 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Validate or replace the standard gdata authorization token."""
7 from __future__ import print_function
14 from chromite.cbuildbot import constants
15 from chromite.lib import cros_build_lib as build_lib
16 from chromite.lib import operation
18 MODULE = os.path.splitext(os.path.basename(__file__))[0]
19 oper = operation.Operation(MODULE)
21 TOKEN_FILE = os.path.join(os.environ['HOME'], '.gdata_token')
22 CRED_FILE = os.path.join(os.environ['HOME'], '.gdata_cred.txt')
25 def _ChrootPathToExternalPath(path):
26 """Translate |path| inside chroot to external path to same location."""
28 return os.path.join(constants.SOURCE_ROOT,
29 constants.DEFAULT_CHROOT_DIR,
35 class OutsideChroot(object):
36 """Class for managing functionality when run outside chroot."""
38 def __init__(self, args):
42 """Re-start |args| inside chroot and copy out auth file."""
44 # Note that enter_chroot (cros_sdk) will automatically copy both
45 # the token file and the cred file into the chroot, so no need
48 # Rerun the same command that launched this run inside the chroot.
49 cmd = [MODULE] + self.args
50 result = build_lib.RunCommand(cmd, enter_chroot=True,
51 print_cmd=False, error_code_ok=True)
52 if result.returncode != 0:
53 oper.Die('Token validation failed, exit code was %r.' %
56 # Copy the token file back from chroot if different.
57 chroot_token_file = _ChrootPathToExternalPath(TOKEN_FILE)
58 if not os.path.exists(chroot_token_file):
59 oper.Die('No token file generated inside chroot.')
60 elif (not os.path.exists(TOKEN_FILE) or not
61 filecmp.cmp(TOKEN_FILE, chroot_token_file)):
62 oper.Notice('Copying new token file from chroot to %r' % TOKEN_FILE)
63 shutil.copy2(chroot_token_file, TOKEN_FILE)
65 oper.Notice('No change in token file.')
68 class InsideChroot(object):
69 """Class for managing functionality when run inside chroot.
71 Note that some additional imports happen within code in this class
72 because those imports are only available inside the chroot.
76 self.creds = None # gdata_lib.Creds object.
77 self.gd_client = None # For interacting with Google Docs.
78 self.it_client = None # For interacting with Issue Tracker.
80 def _LoadTokenFile(self):
81 """Load existing auth token file."""
82 if not os.path.exists(TOKEN_FILE):
83 oper.Warning('No current token file at %r.' % TOKEN_FILE)
86 # Load token file, if it exists.
87 self.creds.LoadAuthToken(TOKEN_FILE)
90 def _SaveTokenFile(self):
91 """Save to auth toke file if anything changed."""
92 self.creds.StoreAuthTokenIfNeeded(TOKEN_FILE)
94 def _ValidateDocsToken(self):
95 """Validate the existing Docs token."""
96 # pylint: disable=W0404
99 if not self.creds.docs_auth_token:
102 oper.Notice('Attempting to log into Docs using auth token.')
103 self.gd_client.source = 'Package Status'
104 self.gd_client.SetClientLoginToken(self.creds.docs_auth_token)
107 # Try to access generic spreadsheets feed, which will check access.
108 self.gd_client.GetSpreadsheetsFeed()
110 # Token accepted. We're done here.
111 oper.Notice('Docs token validated.')
113 except gdata.service.RequestError as ex:
114 reason = ex[0]['reason']
115 if reason == 'Token expired':
120 def _GenerateDocsToken(self):
121 """Generate a new Docs token from credentials."""
122 # pylint: disable=W0404
125 oper.Warning('Docs token not valid. Will try to generate a new one.')
126 self.creds.LoadCreds(CRED_FILE)
127 self.gd_client.email = self.creds.user
128 self.gd_client.password = self.creds.password
131 self.gd_client.ProgrammaticLogin()
132 self.creds.SetDocsAuthToken(self.gd_client.GetClientLoginToken())
134 oper.Notice('New Docs token generated.')
136 except gdata.service.BadAuthentication:
137 oper.Error('Credentials from %r not accepted.'
138 ' Unable to generate new Docs token.' % CRED_FILE)
141 def _ValidateTrackerToken(self):
142 """Validate the existing Tracker token."""
143 # pylint: disable=W0404
145 import gdata.projecthosting.client
147 if not self.creds.tracker_auth_token:
150 oper.Notice('Attempting to log into Tracker using auth token.')
151 self.it_client.source = 'Package Status'
152 self.it_client.auth_token = gdata.gauth.ClientLoginToken(
153 self.creds.tracker_auth_token)
156 # Try to access Tracker Issue #1, which will check access.
157 query = gdata.projecthosting.client.Query(issue_id='1')
158 self.it_client.get_issues('chromium-os', query=query)
160 # Token accepted. We're done here.
161 oper.Notice('Tracker token validated.')
163 except gdata.client.Error:
164 # Exception is gdata.client.Unauthorized in the case of bad token, but
165 # I do not know what the error is for an expired token so I do not
166 # want to limit the catching here. All the errors for gdata.client
167 # functionality extend gdata.client.Error (I do not see one that is
168 # obviously about an expired token).
171 def _GenerateTrackerToken(self):
172 """Generate a new Tracker token from credentials."""
173 # pylint: disable=W0404
176 oper.Warning('Tracker token not valid. Will try to generate a new one.')
177 self.creds.LoadCreds(CRED_FILE)
180 self.it_client.ClientLogin(self.creds.user, self.creds.password,
181 source='Package Status', service='code',
182 account_type='GOOGLE')
183 self.creds.SetTrackerAuthToken(self.it_client.auth_token.token_string)
185 oper.Notice('New Tracker token generated.')
187 except gdata.client.BadAuthentication:
188 oper.Error('Credentials from %r not accepted.'
189 ' Unable to generate new Tracker token.' % CRED_FILE)
193 """Validate existing auth token or generate new one from credentials."""
194 # pylint: disable=W0404
195 import chromite.lib.gdata_lib as gdata_lib
196 import gdata.spreadsheet.service
198 self.creds = gdata_lib.Creds()
199 self.gd_client = gdata.spreadsheet.service.SpreadsheetsService()
200 self.it_client = gdata.projecthosting.client.ProjectHostingClient()
202 self._LoadTokenFile()
204 if not self._ValidateTrackerToken():
205 if not self._GenerateTrackerToken():
206 oper.Die('Failed to validate or generate Tracker token.')
208 if not self._ValidateDocsToken():
209 if not self._GenerateDocsToken():
210 oper.Die('Failed to validate or generate Docs token.')
212 self._SaveTokenFile()
216 usage = 'Usage: %prog'
218 'Run outside of chroot to validate the gdata '
219 'token file at %r or update it if it has expired.\n'
220 'To update the token file there must be a valid '
221 'credentials file at %r.\n'
222 'If run inside chroot the updated token file is '
223 'still valid but will not be preserved if chroot\n'
225 (TOKEN_FILE, CRED_FILE))
227 return optparse.OptionParser(usage=usage, epilog=epilog)
232 # Create a copy of args just to be safe.
235 # No actual options used, but --help is still supported.
236 parser = _CreateParser()
237 (_options, args) = parser.parse_args(argv)
241 oper.Die('No arguments allowed.')
243 if build_lib.IsInsideChroot():
246 OutsideChroot(args).Run()