-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathuploader_clients.py
executable file
·163 lines (138 loc) · 7.2 KB
/
uploader_clients.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Copyright (c) 2017 Mozilla Corporation
# Contributors: Guillaume Destuynder <[email protected]>
import argparse
import difflib
import glob
import json
import logging
import logging.handlers
import os
import sys
from authzero import AuthZero,AuthZeroRule
class DotDict(dict):
"""return a dict.item notation for dict()'s"""
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def __init__(self, dct):
for key, value in dct.items():
if hasattr(value, 'keys'):
value = DotDict(value)
self[key] = value
def find_client_by_id(clients, client_id):
"""
client_id: str - a client id
clients: list - list of client objects
Returns: client object or None
"""
for c in clients:
if (c.get('client_id') == client_id):
return c
return None
if __name__ == "__main__":
# Default credentials loading
try:
with open('credentials.json', 'r') as fd:
credentials = DotDict(json.load(fd))
require_creds = False
except FileNotFoundError:
credentials = DotDict({'client_id': '', 'client_secret': '', 'uri': 'auth-dev.mozilla.auth0.com'})
require_creds = True
# Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--uri', default=credentials.uri, help='URI to Auth0 management API')
parser.add_argument('-c', '--clientid', default=credentials.client_id, required=require_creds, help='Auth0 client id')
parser.add_argument('-s', '--clientsecret', default=credentials.client_secret, required=require_creds, help='Auth0 client secret')
parser.add_argument('-d', '--debug', action="store_true", help='Enable debug mode')
parser.add_argument('-v', '--verbose', action="store_true", help='Show log messages on stderr instead of sending to syslog')
parser.add_argument('-r', '--clients-dir', default='clients', help='Directory containing clients in Auth0 format')
parser.add_argument('--show-diff', action="store_true", help='Show a unified diff of the client being updated on stdout')
parser.add_argument('-g', '--get-clients-only', action="store_true",
help='Get clients from the Auth0 deployment and write them to disk. This will make NO changes '
'to Auth0, but will OVERWRITE all local client files')
args = parser.parse_args()
# Logging
logger = logging.getLogger(__name__)
if not args.verbose:
logger.addHandler(logging.handlers.SysLogHandler(address='/dev/log'))
else:
formatstr="[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s"
logging.basicConfig(format=formatstr, datefmt="%H:%M:%S", stream=sys.stderr)
if args.debug:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
config = DotDict({'client_id': args.clientid, 'client_secret': args.clientsecret, 'uri': args.uri})
authzero = AuthZero(config)
authzero.get_access_token()
logger.debug("Got access token for client_id:{}".format(args.clientid))
# on any error, `authzero` will raise an exception and python will exit with non-zero code
# Remote clients loader
remote_clients = authzero.get_clients()
logger.debug("Loaded {} remote clients from current Auth0 deployment".format(len(remote_clients)))
if not os.path.isdir(args.clients_dir):
raise Exception('NotAClientsDirectory' (args.clients_dir))
# Write remote clients back to disk/local
if args.get_clients_only:
for client in remote_clients:
with open("{}/{}.json".format(args.clients_dir, client.get('client_id')), 'w') as fd:
fd.write(json.dumps(client, indent=4))
logger.debug("Wrote local client configuration {}".format(client.get('client_id')))
sys.exit(0)
# Process all local clients
local_clients_files = glob.glob("{}/*.json".format(args.clients_dir))
local_clients = []
for rfile in local_clients_files:
logger.debug("Reading local clients configuration {}".format(rfile))
with open(rfile, 'r') as fd:
client = DotDict(json.load(fd))
# Match with existing remote client to see if we need to update
client_nr = [i for i,_ in enumerate(remote_clients) if _.get('client_id') == client.client_id]
if client_nr:
# Just in case we have dupe client_id's (this is not supposed to be possible)
if (len(client_nr) > 1):
raise Exception('ClientMatchByIdFailed', (client.name, client_nr))
else:
logger.debug('Client only exists locally, considered new and to be created: {}'.format(rule.name))
local_clients.append(client)
logger.debug("Found {} local clients".format(len(local_clients)))
# Find dead clients (i.e. to remove clients that only exist remotely)
remove_clients = []
for rl in local_clients:
if (find_client_by_id(local_clients, rl.client_id) is None):
remove_clients.append(rr)
continue
logger.debug("Found {} clients that not longer exist locally and will be deleted remotely".format(len(remove_clients)))
# Update or create (or delete) clients as needed
for r in remove_clients:
logger.debug("Removing remote client {} ({}) from Auth0".format(r.name, r.client_id))
authzero.delete_client(r.client_id)
## Update & Create (I believe this may be atomic swaps for updates)
for lclient in local_clients:
if not lclient.client_id:
logger.debug("Creating new remote client {} on Auth0".format(lclient.name))
noop
ret = authzero.create_client(r)
logger.info("New remote client {} ({}) created on Auth0".format(ret.get('client_id'), ret.get('name')))
else:
rclient = find_client_by_id(remote_clients, lclient.client_id)
if (rclient == lclient):
logger.debug("Remote and local clients for id {} ({}) are identical, no need to "
"update".format(lclient.client_id, lclient.name))
continue
# Show pretty informational unified diff if requested, of the changes to be pushed to Auth0
if args.show_diff:
diff = difflib.unified_diff(json.dumps(rclient, indent=4).split('\n'),
json.dumps(lclient, indent=4).split('\n'),
fromfile="previous_{}.json".format(lclient.client_id),
tofile="new_{}.json".format(lclient.client_id))
print("Unified diff for client {} ({})".format(lclient.client_id, lclient.name))
print('\n'.join(diff))
logger.debug("Updating remote client {} ({}) on Auth0".format(lclient.name, lclient.client_id))
authzero.update_client(lclient.client_id, lclient)
logger.info("Client {} ({}) has been updated on Auth0".format(client.name, lclient.client_id))
sys.exit(0)