Switch to unified view

a/scripts/update-acls.py b/scripts/update-acls.py
1
import logging
1
import logging
2
from optparse import OptionParser
2
from optparse import OptionParser
3
from pprint import pformat
3
from pprint import pformat
4
4
5
import bson
5
from pylons import c
6
from pylons import c
6
from ming.base import Object
7
from ming.base import Object
7
from ming.orm import ThreadLocalORMSession
8
8
9
from allura import model as M
9
from allura import model as M
10
from allura.command.show_models import dfs, build_model_inheritance_graph
10
from allura.command.show_models import dfs, build_model_inheritance_graph
11
11
12
log = logging.getLogger('update-acls')
12
log = logging.getLogger('update-acls')
13
13
14
options = None
14
options = None
15
optparser = OptionParser(usage='allurapaste script <ini file> -- %prog [options] [neighborhood1...]')
15
optparser = OptionParser(usage='allurapaste script <ini file> -- %prog [options] [neighborhood1...]')
16
optparser.add_option('-t', '--test',  dest='test', action='store_true')
16
optparser.add_option('-t', '--test',  dest='test', action='store_true')
17
17
18
main_db = M.main_doc_session.db
19
c_neighborhood =  main_db.neighborhood
20
c_project =  main_db.project
21
c_user = main_db.user
22
c_project_role = main_db.project_role
23
c.project = Object(
24
    database_uri=c_project.find().next()['database_uri'])
25
26
project_db = M.project_doc_session.db
27
c_app_config = project_db.config
28
18
def main():
29
def main():
19
    global options
30
    global options
20
    options, neighborhoods = optparser.parse_args()
31
    options, neighborhoods = optparser.parse_args()
21
    neighborhood = M.main_doc_session.db.neighborhood
22
    project = M.main_doc_session.db.project
23
    c.project = Object(
24
        database_uri=project.find().next()['database_uri'])
25
    app_config = M.project_doc_session.db.config
26
    if neighborhoods:
32
    if neighborhoods:
27
        log.info('Updating neighborhoods: %s', neighborhoods)
33
        log.info('Updating neighborhoods: %s', neighborhoods)
28
        q_neighborhoods = list(neighborhood.find(dict(name={'$in': neighborhoods })))
34
        q_neighborhoods = list(c_neighborhood.find(dict(name={'$in': neighborhoods })))
29
        neighborhood_ids=[ n['_id'] for n in q_neighborhoods ]
35
        neighborhood_ids=[ n['_id'] for n in q_neighborhoods ]
30
        q_projects = list(project.find(dict(neighborhood_id={'$in': neighborhood_ids})))
36
        q_projects = list(c_project.find(dict(neighborhood_id={'$in': neighborhood_ids})))
31
        project_ids = list(p['_id'] for p in q_projects)
37
        project_ids = list(p['_id'] for p in q_projects)
32
        q_app_config = list(app_config.find(dict(project_id={'$in': project_ids})))
38
        q_app_config = list(c_app_config.find(dict(project_id={'$in': project_ids})))
33
        log.info('... %d neighborhoods', len(q_neighborhoods))
39
        log.info('... %d neighborhoods', len(q_neighborhoods))
34
        log.info('... %d projects', len(q_projects))
40
        log.info('... %d projects', len(q_projects))
35
        log.info('... %d app configs', len(q_app_config))
41
        log.info('... %d app configs', len(q_app_config))
36
    else:
42
    else:
37
        q_neighborhoods = neighborhood.find()
43
        q_neighborhoods = c_neighborhood.find()
38
        q_projects = project.find()
44
        q_projects = c_project.find()
39
        q_app_config = app_config.find()
45
        q_app_config = c_app_config.find()
40
        log.info('Updating all neighborhoods')
46
        log.info('Updating all neighborhoods')
41
    # Update project acls
47
    # Update project acls
42
    log.info('====================================')
48
    log.info('====================================')
43
    log.info('Update project ACLs')
49
    log.info('Update project ACLs')
44
    for p in q_projects:
50
    for p in q_projects:
45
        update_project_acl(p)
51
        update_project_acl(p)
46
        if not options.test: project.save(p)
52
        if not options.test: c_project.save(p)
47
    # Update neighborhood acls
53
    # Update neighborhood acls
48
    log.info('====================================')
54
    log.info('====================================')
49
    log.info('Update neighborhood ACLs')
55
    log.info('Update neighborhood ACLs')
50
    for n in q_neighborhoods:
56
    for n in q_neighborhoods:
51
        p = project.find(dict(
57
        p = c_project.find(dict(
52
                neighborhood_id=n['_id'], shortname='--init--')).next()
58
                neighborhood_id=n['_id'], shortname='--init--')).next()
53
        update_neighborhood_acl(n, p)
59
        update_neighborhood_acl(n,p)
54
        if not options.test:
60
        if not options.test:
55
            neighborhood.save(n)
61
            c_neighborhood.save(n)
56
            project.save(p)
62
            c_project.save(p)
57
            ThreadLocalORMSession.flush_all()
58
            ThreadLocalORMSession.close_all()
59
    graph = build_model_inheritance_graph()
63
    graph = build_model_inheritance_graph()
60
    # Update app config acls
64
    # Update app config acls
61
    log.info('====================================')
65
    log.info('====================================')
62
    log.info('Update appconfig ACLs')
66
    log.info('Update appconfig ACLs')
63
    for ac in q_app_config:
67
    for ac in q_app_config:
64
        simple_acl_update(ac)
68
        simple_acl_update(ac, 'app_config')
65
        if not options.test: app_config.save(ac)
69
        if not options.test: c_app_config.save(ac)
66
        # Update artifact acls
70
        # Update artifact acls
67
        log.info('====================================')
71
        log.info('====================================')
68
        log.info('Update artifact ACLs')
72
        log.info('Update artifact ACLs for %s', ac['_id'])
69
        for _, a_cls in dfs(M.Artifact, graph):
73
        for _, a_cls in dfs(M.Artifact, graph):
70
            artifact = M.project_doc_session.db[
74
            c_artifact = project_db[a_cls.__mongometa__.name]
71
                a_cls.__mongometa__.name]
72
            for a in artifact.find(dict(app_config_id=ac['_id'])):
75
            for a in c_artifact.find(dict(app_config_id=ac['_id'])):
73
                empty_acl = not a['acl']
76
                empty_acl = not a['acl']
74
                simple_acl_update(a)
77
                simple_acl_update(a, a_cls.__mongometa__.name)
75
                if not options.test and not empty_acl: artifact.save(a)
78
                if not options.test and not empty_acl: c_artifact.save(a)
76
79
77
def update_project_acl(project_doc):
80
def update_project_acl(project_doc):
78
    '''Convert the old dict-style ACL to a list of ALLOW ACEs. Also move the
81
    '''Convert the old dict-style ACL to a list of ALLOW ACEs. Also move the
79
    security,tool,delete perms to 'admin'
82
    security,tool,delete perms to 'admin'
80
    '''
83
    '''
81
    project_role = M.project_doc_session.db.project_role
82
    if not isinstance(project_doc['acl'], dict):
84
    if not isinstance(project_doc['acl'], dict):
83
        log.warning('Project %s is already updated', project_doc['shortname'])
85
        log.warning('Project %s is already updated', project_doc['shortname'])
84
        return
86
        return
85
    perm_map = dict(
87
    perm_map = dict(
86
        read='read',
88
        read='read',
...
...
91
        delete='admin')
93
        delete='admin')
92
    new_acl = []
94
    new_acl = []
93
    for perm, role_ids in sorted(project_doc['acl'].iteritems()):
95
    for perm, role_ids in sorted(project_doc['acl'].iteritems()):
94
        perm = perm_map[perm]
96
        perm = perm_map[perm]
95
        for rid in role_ids:
97
        for rid in role_ids:
96
            if project_role.find(dict(_id=rid)).count() == 0: continue
98
            if c_project_role.find(dict(_id=rid)).count() == 0: continue
97
            _grant(new_acl, perm, rid)
99
            _grant(new_acl, perm, rid)
98
    if options.test:
100
    if options.test:
99
        log.info('--- update %s\n%s\n%s\n---',
101
        log.info('--- update %s\n%s\n%s\n---',
100
                 project_doc['shortname'],
102
                 project_doc['shortname'],
101
                 pformat(_format_acd(project_doc['acl'])),
103
                 pformat(_format_acd(project_doc['acl'])),
...
...
105
def update_neighborhood_acl(neighborhood_doc, init_doc):
107
def update_neighborhood_acl(neighborhood_doc, init_doc):
106
    '''Convert nbhd admins users to --init-- project admins'''
108
    '''Convert nbhd admins users to --init-- project admins'''
107
    if options.test: log.info('Update nbhd %s', neighborhood_doc['name'])
109
    if options.test: log.info('Update nbhd %s', neighborhood_doc['name'])
108
    if 'acl' not in neighborhood_doc:
110
    if 'acl' not in neighborhood_doc:
109
        log.warning('Neighborhood %s already updated', neighborhood_doc['name'])
111
        log.warning('Neighborhood %s already updated', neighborhood_doc['name'])
112
        return
110
    p = Object(init_doc)
113
    p = Object(init_doc)
111
    p.root_project=p
114
    p.root_project=p
112
    r_auth = M.ProjectRole.authenticated(p)._id
115
    r_anon = _project_role(init_doc['_id'], '*anonymous')
113
    r_admin = M.ProjectRole.by_name('Admin', p)._id
116
    r_auth = _project_role(init_doc['_id'], '*authenticated')
117
    r_admin = _project_role(init_doc['_id'], 'Admin')
114
    acl = neighborhood_doc['acl']
118
    acl = neighborhood_doc['acl']
115
    new_acl = list(init_doc['acl'])
119
    new_acl = list(init_doc['acl'])
116
    assert acl['read'] == [None] # nbhd should be public
120
    assert acl['read'] == [None] # nbhd should be public
117
    for uid in acl['admin'] + acl['moderate']:
121
    for uid in acl['admin'] + acl['moderate']:
118
        u = M.User.query.get(_id=uid)
122
        u = c_user.find(dict(_id=uid)).next()
123
        if options.test:
119
        if options.test: log.info('... grant nbhd admin to: %s', u.username)
124
            log.info('... grant nbhd admin to: %s', u['username'])
120
        role =  M.ProjectRole.upsert(user_id=uid, project_id=init_doc['_id'])
125
            continue
126
        role =  _project_role(init_doc['_id'], user_id=uid)
121
        if r_admin not in role.roles:
127
        if r_admin['_id'] not in role['roles']:
122
            role.roles.append(r_admin)
128
            role['roles'].append(r_admin['_id'])
129
            c_project_role.save(role)
130
    _grant(new_acl, 'read', r_anon['_id'])
131
    _grant(new_acl, 'admin', r_admin['_id'])
123
    _grant(new_acl, 'register', r_admin)
132
    _grant(new_acl, 'register', r_admin['_id'])
124
    if acl['create'] == [ ]:
133
    if acl['create'] == [ ]:
125
        if options.test: log.info('grant register to auth')
134
        if options.test: log.info('grant register to auth')
126
        _grant(new_acl, 'register', r_auth)
135
        _grant(new_acl, 'register', r_auth['_id'])
127
    del neighborhood_doc['acl']
136
    del neighborhood_doc['acl']
128
    if options.test:
137
    if options.test:
129
        log.info('--- new init acl:\n%s\n%s\n---',
138
        log.info('--- new init acl:\n%s\n%s\n---',
130
                 pformat(_format_acd(init_doc['acl'])),
139
                 pformat(_format_acd(init_doc['acl'])),
131
                 pformat(map(_format_ace, new_acl)))
140
                 pformat(map(_format_ace, new_acl)))
132
    init_doc['acl'] = new_acl
141
    init_doc['acl'] = new_acl
133
142
143
def _project_role(project_id, name=None, user_id=None):
144
    doc = dict(project_id=project_id)
145
    if name:
146
        doc['name'] = name
147
    else:
148
        doc['user_id'] = user_id
149
    for role in c_project_role.find(doc):
150
        return role
151
    assert name is None
152
    doc.update(
153
        _id=bson.ObjectId(),
154
        roles=[])
155
    c_project_role.save(doc)
156
    return doc
157
                                 
158
134
def simple_acl_update(doc):
159
def simple_acl_update(doc, collection_name):
135
    '''Update dict-style to list-style ACL'''
160
    '''Update dict-style to list-style ACL'''
136
    if not isinstance(doc['acl'], dict):
161
    if not isinstance(doc['acl'], dict):
137
        log.warning('Already upgraded %s' % doc)
162
        log.warning('Already upgraded %s: %s', collection_name, doc)
138
        return
163
        return
139
164
140
    new_acl = []
165
    new_acl = []
141
    for perm, role_ids in sorted(doc['acl'].iteritems()):
166
    for perm, role_ids in sorted(doc['acl'].iteritems()):
142
        for rid in role_ids:
167
        for rid in role_ids:
143
            _grant(new_acl, perm, rid)
168
            _grant(new_acl, perm, rid)
144
    if options.test and doc['acl']:
169
    if options.test and doc['acl']:
145
        log.info('--- update\n%s\n%s\n---',
170
        log.info('--- update %s %s\n%s\n%s\n---',
171
                 collection_name, doc['_id'],
146
                 pformat(_format_acd(doc['acl'])),
172
                 pformat(_format_acd(doc['acl'])),
147
                 pformat(map(_format_ace, new_acl)))
173
                 pformat(map(_format_ace, new_acl)))
148
    doc['acl'] = new_acl
174
    doc['acl'] = new_acl
149
175
150
def _grant(acl, permission, role_id):
176
def _grant(acl, permission, role_id):
...
...
159
    if isinstance(ace, basestring): return ace
185
    if isinstance(ace, basestring): return ace
160
    return '(%s, %s, %s)' % (
186
    return '(%s, %s, %s)' % (
161
        ace['access'], ace['permission'], _format_role(ace['role_id']))
187
        ace['access'], ace['permission'], _format_role(ace['role_id']))
162
188
163
def _format_role(rid):
189
def _format_role(rid):
164
    role = M.ProjectRole.query.get(_id=rid)
190
    for role in c_project_role.find(dict(_id=rid)):
165
    if role:
166
        if role.name:
191
        if role['name']:
167
            return role.name
192
            return role['name']
168
        if role.user:
193
        if role['user_id']:
194
            u = c_user.find(_id=role['user_id']).next()
169
            return role.user.username
195
            return u['username']
196
        break
170
    return '--invalid--'
197
    return '--invalid--'
171
198
172
def _format_acd(acd):
199
def _format_acd(acd):
173
    return dict(
200
    return dict(
174
        (k, map(_format_role, v))
201
        (k, map(_format_role, v))