Skip to content

Commit c729c8e

Browse files
committed
[refactor] Made managers pluggable
1 parent fe3fea5 commit c729c8e

File tree

14 files changed

+582
-463
lines changed

14 files changed

+582
-463
lines changed

mme_server/auth.py

Lines changed: 5 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -6,162 +6,35 @@
66
"""
77
from __future__ import with_statement, division, unicode_literals
88

9-
import json
109
import logging
1110
import flask
1211

1312
from functools import wraps
1413

1514
from flask import request, jsonify
16-
from elasticsearch import Elasticsearch
17-
from elasticsearch_dsl import Search, Q
1815

19-
from .datastore import ESIndex
20-
from .models import get_backend
16+
from .backend import get_backend
2117

2218

2319
logger = logging.getLogger(__name__)
2420

2521

26-
def get_server_manager():
27-
manager = getattr(flask.g, '_mme_servers', None)
28-
if manager is None:
29-
manager = flask.g._mme_servers = ServerManager()
30-
31-
return manager
32-
33-
3422
def auth_token_required():
3523
def decorator(f):
3624
@wraps(f)
3725
def decorated_function(*args, **kwargs):
3826
logger.info("Authenticating request")
3927
token = request.headers.get('X-Auth-Token')
40-
auth = get_server_manager()
41-
server = auth.verify(token)
28+
backend = get_backend()
29+
servers = backend.get_manager('servers')
30+
server = servers.verify(token)
4231
if not server:
4332
error = jsonify(message='X-Auth-Token not authorized')
4433
error.status_code = 401
4534
return error
4635

36+
# Set authenticated server as flask global for request
4737
flask.g.server = server
4838
return f(*args, **kwargs)
4939
return decorated_function
5040
return decorator
51-
52-
53-
class ServerManager:
54-
FIELDS = ['server_id', 'server_label', 'server_key', 'direction', 'base_url']
55-
DOC_CONFIG = {
56-
'properties': {
57-
'server_id': {
58-
'type': 'string',
59-
'index': 'not_analyzed',
60-
},
61-
'server_label': {
62-
'type': 'string',
63-
'index': 'not_analyzed',
64-
},
65-
'server_key': {
66-
'type': 'string',
67-
'index': 'not_analyzed',
68-
},
69-
'direction': {
70-
'type': 'string',
71-
'index': 'not_analyzed',
72-
},
73-
'base_url': {
74-
'type': 'string',
75-
'index': 'not_analyzed',
76-
}
77-
}
78-
}
79-
80-
def __init__(self, backend=None):
81-
if backend is None:
82-
backend = Elasticsearch()
83-
84-
self._index = ESIndex(db=backend,
85-
name='servers',
86-
doc_type='server',
87-
doc_config=self.DOC_CONFIG)
88-
89-
@property
90-
def index(self):
91-
return self._index
92-
93-
def add(self, server_id, server_label, server_key, direction, base_url):
94-
assert server_id and server_key and direction in ['in', 'out']
95-
96-
if base_url and not base_url.startswith('https://'):
97-
logger.error('base URL must start with "https://"')
98-
return
99-
100-
self.index.ensure_exists()
101-
102-
data = {
103-
'server_id': server_id,
104-
'server_label': server_label,
105-
'server_key': server_key,
106-
'direction': direction,
107-
'base_url': base_url,
108-
}
109-
110-
# If it already exists, update
111-
s = self.index.search()
112-
s = s.filter('term', server_id=server_id)
113-
s = s.filter('term', direction=direction)
114-
results = s.execute()
115-
116-
if results.hits.total > 1:
117-
logger.error('Found two or more matching server entries')
118-
else:
119-
id = None
120-
if results.hits.total == 1:
121-
# Found a match, so update instead
122-
id = results.hits[0].meta.id
123-
124-
self.index.save(id=id, doc=data)
125-
logger.info("Authorized server:\n{}".format(json.dumps(data, indent=4, sort_keys=True)))
126-
# Refresh index to ensure immediately usable
127-
self.index.refresh()
128-
129-
def remove(self, server_id, direction):
130-
if self.index.exists():
131-
s = self.index.search()
132-
s = s.filter('term', server_id=server_id)
133-
134-
if direction:
135-
s = s.filter('term', direction=direction)
136-
results = s.execute()
137-
138-
for hit in results:
139-
id = hit.meta.id
140-
self.index.delete(id=id)
141-
logger.info("Deleted server:{} direction:{}".format(hit.server_id, hit.direction))
142-
143-
def list(self):
144-
rows = []
145-
if self.index.exists():
146-
s = self.index.search()
147-
s = s.query('match_all')
148-
149-
# Iterate through all, using scan
150-
for hit in s.scan():
151-
row = dict([(field, hit[field]) for field in self.FIELDS])
152-
rows.append(row)
153-
154-
return {
155-
'fields': self.FIELDS,
156-
'rows': rows
157-
}
158-
159-
def verify(self, key):
160-
if key and self.index.exists():
161-
s = self.index.search()
162-
s = s.filter('term', server_key=key)
163-
s = s.filter('term', direction='in')
164-
results = s.execute()
165-
166-
if results.hits:
167-
return results.hits[0]

mme_server/backend.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""
2+
Module for accessing the backend connection
3+
"""
4+
5+
from __future__ import with_statement, division, unicode_literals
6+
7+
import logging
8+
import flask
9+
10+
from elasticsearch import Elasticsearch
11+
12+
from .managers import Managers
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
18+
def get_backend():
19+
backend = getattr(flask.g, '_mme_backend', None)
20+
if backend is None:
21+
backend = flask.g._mme_backend = Managers(Elasticsearch())
22+
23+
return backend

mme_server/cli.py

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010

1111
from binascii import hexlify
1212

13-
from .auth import get_server_manager
13+
from .backend import get_backend
1414
from .compat import urlretrieve
15-
from .models import get_backend
1615
from .server import app
1716

1817

@@ -59,10 +58,12 @@ def index_file(index, filename, url):
5958

6059
with app.app_context():
6160
backend = get_backend()
61+
patients = backend.get_manager('patients')
62+
vocabularies = backend.get_manager('vocabularies')
6263
index_funcs = {
63-
'hpo': backend.vocabularies.index_hpo,
64-
'genes': backend.vocabularies.index_genes,
65-
'patients': backend.patients.index_file,
64+
'hpo': vocabularies.index_hpo,
65+
'genes': vocabularies.index_genes,
66+
'patients': patients.index_file,
6667
}
6768
index_funcs[index](filename=filename)
6869

@@ -76,64 +77,83 @@ def fetch_resource(filename, url):
7677
logger.info('Saved file to: {}'.format(filename))
7778

7879

79-
def list_servers():
80+
def list_servers(direction='out'):
8081
with app.app_context():
81-
servers = get_server_manager()
82-
response = servers.list()
82+
backend = get_backend()
83+
servers = backend.get_manager('servers')
84+
response = servers.list(direction=direction)
8385
# print header
8486
fields = response['fields']
8587
print('\t'.join(fields))
8688

8789
for server in response.get('rows', []):
8890
print('\t'.join([repr(server[field]) for field in fields]))
8991

92+
def list_clients():
93+
return list_servers(direction='in')
9094

91-
def add_server(id, direction, key=None, label=None, base_url=None):
95+
def add_server(id, direction='out', key=None, label=None, base_url=None):
9296
if not label:
9397
label = id
9498

9599
if direction == 'out' and not base_url:
96100
raise Exception('--base-url must be specified for outgoing servers')
97101

98102
with app.app_context():
99-
servers = get_server_manager()
103+
backend = get_backend()
104+
servers = backend.get_manager('servers')
100105
# Generate a random key if one was not provided
101106
if key is None:
102107
key = hexlify(os.urandom(30)).decode()
103108
servers.add(server_id=id, server_key=key, direction=direction, server_label=label, base_url=base_url)
104109

110+
def add_client(id, key=None, label=None):
111+
add_server(id, 'in', key=key, label=label)
105112

106-
def remove_server(id, direction):
113+
def remove_server(id, direction='out'):
107114
with app.app_context():
108-
servers = get_server_manager()
115+
backend = get_backend()
116+
servers = backend.get_manager('servers')
109117
servers.remove(server_id=id, direction=direction)
110118

119+
def remove_client(id):
120+
remove_server(id, direction='in')
121+
111122

112123
def run_tests():
113124
suite = unittest.TestLoader().discover('.'.join([__package__, 'tests']))
114125
unittest.TextTestRunner().run(suite)
115126

116127

117-
def add_auth_parser(parser):
118-
auth_parser = parser.add_parser('auth', description="Authorization sub-commands")
119-
subparsers = auth_parser.add_subparsers(title='subcommands')
120-
subparser = subparsers.add_parser('add', description="Add server authorization")
121-
subparser.add_argument("id", help="A unique server identifier")
122-
subparser.add_argument("direction", choices=["in", "out"],
123-
help="Direction of server authorization, 'in': the other server can send requests, 'out': this server can send requests")
124-
subparser.add_argument("--key", help="The secret key used to authenticate requests to/from the server (default: randomly generate a secure key)")
125-
subparser.add_argument("--label", help="The display name for the server ")
126-
subparser.add_argument("--base-url", dest="base_url", help="The base URL for sending API requests to the other server (e.g., <base-url>/match should be a valid endpoint). Must be specified for outgoing requests")
127-
subparser.set_defaults(function=add_server)
128+
def add_server_subcommands(parser, direction):
129+
"""Add subparser for incoming or outgoing servers
130+
131+
direction - 'in': incoming servers, 'out': outgoing servers
132+
"""
133+
server_type = 'client' if direction == 'in' else 'server'
134+
subparsers = parser.add_subparsers(title='subcommands')
135+
subparser = subparsers.add_parser('add', description="Add {} authorization".format(server_type))
136+
subparser.add_argument("id", help="A unique {} identifier".format(server_type))
137+
subparser.add_argument("--key", help="The secret key used to authenticate requests to/from the {} (default: randomly generate a secure key)".format(server_type))
138+
subparser.add_argument("--label", help="The display name for the {}".format(server_type))
139+
if server_type == 'server':
140+
subparser.add_argument("--base-url", dest="base_url", help="The base URL for sending API requests to the server (e.g., <base-url>/match should be a valid endpoint). Must be specified for outgoing requests")
141+
subparser.set_defaults(function=add_server)
142+
else:
143+
subparser.set_defaults(function=add_client)
128144

129-
subparser = subparsers.add_parser('rm', description="Remove server authorization")
130-
subparser.add_argument("id", help="Server identifier")
131-
subparser.add_argument("direction", choices=["in", "out"],
132-
help="Direction of server authorization, 'in': the other server can send requests, 'out': this server can send requests")
133-
subparser.set_defaults(function=remove_server)
145+
subparser = subparsers.add_parser('rm', description="Remove {} authorization".format(server_type))
146+
subparser.add_argument("id", help="The {} identifier".format(server_type))
147+
if server_type == 'server':
148+
subparser.set_defaults(function=remove_server)
149+
else:
150+
subparser.set_defaults(function=remove_client)
134151

135-
subparser = subparsers.add_parser('list', description="List all existing server authorizations")
136-
subparser.set_defaults(function=list_servers)
152+
subparser = subparsers.add_parser('list', description="List {} authorizations".format(server_type))
153+
if server_type == 'server':
154+
subparser.set_defaults(function=list_servers)
155+
else:
156+
subparser.set_defaults(function=list_clients)
137157

138158

139159
def parse_args(args):
@@ -174,7 +194,11 @@ def parse_args(args):
174194
help="The host the server will listen to (0.0.0.0 to listen globally; 127.0.0.1 to listen locally; default: %(default)s)")
175195
subparser.set_defaults(function=app.run)
176196

177-
add_auth_parser(subparsers)
197+
subparser = subparsers.add_parser('servers', description="Server authorization sub-commands")
198+
add_server_subcommands(subparser, direction='out')
199+
200+
subparser = subparsers.add_parser('clients', description="Client authorization sub-commands")
201+
add_server_subcommands(subparser, direction='in')
178202

179203
subparser = subparsers.add_parser('test', description="Run tests")
180204
subparser.set_defaults(function=run_tests)

0 commit comments

Comments
 (0)