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

Side by Side Diff: tools/origin_trials/generate_token.py

Issue 1578793002: Add experimental framework token generation tool (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Move third_party code; fix bug from last patch's rename Created 4 years, 10 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
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2016 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Utility for generating experimental API tokens
7
8 usage: generate_token.py [-h] [--key-file KEY_FILE]
9 [--expire-days EXPIRE_DAYS |
10 --expire-timestamp EXPIRE_TIMESTAMP]
11 origin trial_name
12
13 Run "generate_token.py -h" for more help on usage.
14 """
15 import argparse
16 import base64
17 import re
18 import os
19 import sys
20 import time
21 import urlparse
22
23 from third_party.ed25519 import ed25519
Nico 2016/01/26 17:27:46 With a snippet like e.g. https://code.google.com/p
iclelland 2016/02/12 22:00:50 I hate to hack the import path; Python has a well-
24
25 HOSTNAME_REGEX = re.compile(
palmer 2016/02/11 22:03:20 http://stackoverflow.com/a/2532344 looks more corr
iclelland 2016/02/16 20:25:12 Certainly. It handles the 255 character limit, as
26 r"""^
27 # Zero or more components, where each one is either a single alphanumeric
28 (([a-z0-9]|
29 # Or multiple alphanumerics with internal hyphens
30 [a-z0-9][a-z0-9-]*[a-z0-9])
31 # Separated by periods
32 \.)*
33 # And followed by a final component.
34 ([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$""",
35 re.IGNORECASE | re.VERBOSE
36 )
37
38 def OriginFromArgs(arg):
palmer 2016/02/11 22:03:20 Nit: The function name suggests that the argument
iclelland 2016/02/12 22:00:50 It is a convention, but you're right that it's a m
39 """Constructs the origin for the token from the command line arguments.
40
41 Returns None if this is not possible (neither a valid hostname nor a
42 valid origin URL was provided.)
43 """
44 # Does it look like a hostname?
45 if HOSTNAME_REGEX.match(arg):
46 return 'https://'+arg
47 # Try to construct an origin URL from the argument
48 origin = urlparse.urlparse(arg)
49 if not origin or not origin.scheme or not origin.netloc:
50 raise argparse.ArgumentTypeError("%s is not a hostname or a URL" % arg)
51 # Strip any other components and return the URL:
52 return urlparse.urlunparse(origin[:2] + ('', )*4)
palmer 2016/02/11 22:03:20 It's a bit mysterious (to me, at least) what urlun
palmer 2016/02/11 22:03:20 Nit: consistently use spaces around operators.
iclelland 2016/02/12 22:00:50 I would, but PEP8 says, "If operators with differe
iclelland 2016/02/16 20:25:12 Unparse is just the opposite of parse -- parse bre
53
54 def ExpiryFromArgs(args):
55 if args.expire_timestamp:
56 return int(args.expire_timestamp)
57 return ((int(time.time()) + (int(args.expire_days) * 86400)) * 1000)
58
59 def GenerateTokenData(origin, apiName, expiry):
60 return "{0}|{1}|{2}".format(origin, apiName, expiry)
61
62 def Sign(private_key, data):
palmer 2016/02/11 22:03:20 Nit: Consistently use private_key or privateKey st
iclelland 2016/02/12 22:00:50 Thanks. I missed apiName on my first pass. PEP8 st
63 return ed25519.signature(data, private_key[:32], private_key[32:])
64
65 def FormatToken(signature, data):
66 return base64.b64encode(signature) + "|" + data
palmer 2016/02/11 22:03:20 Nit: Use "..." or '...' strings throughout; whatev
iclelland 2016/02/12 22:00:50 Done. The standard explicitly makes no recommendat
67
68 def main():
69 parser = argparse.ArgumentParser(
70 description="Generate tokens for enabling experimental APIs")
71 parser.add_argument('origin',
72 help="Origin for which to enable the API. This can be a "
palmer 2016/02/11 22:03:20 Nit: Punctuation and conciseness. How about: Orig
iclelland 2016/02/12 22:00:50 Done.
73 "bare hostname (in which case https will be "
74 "assumed,) or a valid origin URL (hostname, "
75 "protocol and port can all be included.)",
76 type=OriginFromArgs)
77 parser.add_argument('trial_name',
78 help="Feature to enable. The current list of "
79 "experimental feature trials can be found in "
80 "RuntimeFeatures.in")
81 parser.add_argument('--key-file',
82 help='Ed25519 private key file to sign the token with',
83 default="eftest.key")
84 expiry_group = parser.add_mutually_exclusive_group()
85 expiry_group.add_argument('--expire-days',
86 help='Days from now when the token should exipire',
87 type=int,
88 default=42)
89 expiry_group.add_argument('--expire-timestamp',
90 help="Exact time (milliseconds since 1970-01-01 "
91 "00:00:00 UTC) when the token should exipire",
92 type=int)
93
94 args = parser.parse_args()
95 expiry = ExpiryFromArgs(args)
96
97 key_file = open(os.path.expanduser(args.key_file))
98 private_key = key_file.read()
palmer 2016/02/11 22:03:20 Maybe just read the 1st 64 bytes from the file, an
iclelland 2016/02/12 22:00:50 Yeah, the key is a binary file; it wouldn't make s
99
100 # Validate that the key file read was a proper Ed25519 key -- running the
101 # publickey method on the first half of the key should return the second
102 # half.
103 if (len(private_key) != 64 or
104 ed25519.publickey(private_key[:32]) != private_key[32:]):
105 print("Unable to use the specified private key file.")
106 sys.exit(1)
107
108 token_data = GenerateTokenData(args.origin, args.trial_name, expiry)
109 signature = Sign(private_key, token_data)
110
111 # Verify that that the signature is correct before printing it.
112 try:
113 ed25519.checkvalid(signature, token_data, private_key[32:])
114 except Exception:
115 print "There was an error generating the signature."
palmer 2016/02/11 22:03:20 Maybe print the exception in case it has anything
iclelland 2016/02/12 22:00:50 For diagnostics, sure. The exceptions are mostly n
116 sys.exit(1)
117
118 print FormatToken(signature, token_data)
119
120 if __name__ == "__main__":
121 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698