eidawsauth.py 9.19 KB
Newer Older
1
import gnupg
2
3
4
5
import re
import datetime
import random
import string
6
import logging
7
8
import os
from hashlib import md5
9
from flask import Flask, request, Response
10
import configurations
11
import psycopg2
12
from version import __version__
13
import importlib
14

15
application = Flask(__name__)
16
17
18
19
20
21
22
23

if 'RUNMODE' in os.environ:
    try:
        importlib.import_module('.'+os.environ.get('RUNMODE'), 'configurations')
        application.config.from_object('configurations.'+os.environ.get('RUNMODE')+'.config')
    except Exception as e:
        raise e
        # no configuration for this RUNMODE value
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
24
else:
25
    application.config.from_object('configurations.default.config')
26

27
28
29
30
31
32
33
34
35
36
37
38
from logging.config import dictConfig
dictConfig({
    'version': 1,
    'formatters': {'default': {
        'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
    }},
    'handlers': {'wsgi': {
        'class': 'logging.StreamHandler',
        'stream': 'ext://flask.logging.wsgi_errors_stream',
        'formatter': 'default'
    }},
    'root': {
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
39
        'level': application.config['LOGLEVEL'],
40
41
42
43
        'handlers': ['wsgi']
    }
})

44
45
46
47
48
49
def wsshash(login, password):
    """
    Compute a hash suitable for the IRIS wss stack.
    """
    return md5(("%s:FDSN:%s"%(login,password)).encode()).hexdigest()

50
51
52
53
54
55
56
57
58
def verify_token_signature(data, gpg_homedir):
    # First we verify the signature
    gpg = gnupg.GPG(gnupghome=gpg_homedir)
    verified = gpg.verify(data)
    if not verified: raise ValueError("Signature could not be verified!")
    return True

def parse_input_data(data):
    # Then we get the token :
59
    token = re.search(r'{(?P<token>.*)}',str(data)).groupdict()['token']
60
61
62
63
64
65
    logging.debug(token)
    d = dict([i for i in kv.split(':',1)] for kv in token.replace('"','').replace(' ','').split(','))

    logging.debug("Transformed to dictionary : "+str(d))
    return d

66
def register_login(login, password):
67
68
    """
    - Connects to the AUTHDB
69
    - Generate Login and Password hash
70
71
    - register in the users table
    """
72
    expiration_time=datetime.datetime.now()+datetime.timedelta(days=1)
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    try:
        conn = psycopg2.connect(dbname= application.config['AUTHDBNAME'],
                            port = application.config['AUTHDBPORT'],
                            host = application.config['AUTHDBHOST'],
                            user= application.config['AUTHDBUSER'],
                            password = application.config['AUTHDBPASSWORD'])
        cur = conn.cursor()
        logging.debug("Connected to users database")
    except Exception as e:
        logging.error("Unable to connect to database %s as %s@%s:%s"%(application.config['AUTHDBNAME'],
                                                                      application.config['AUTHDBUSER'],
                                                                      application.config['AUTHDBHOST'],
                                                                      application.config['AUTHDBPORT']))
        raise e
87

Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
88
    cur.execute("""
89
                INSERT INTO users VALUES (DEFAULT, %(login)s, 'Temp', 'EIDA', %(tmpmail)s, %(expires_at)s);
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
90
                """,
91
                {'login': login, 'tmpmail': "%s@eida"%(login),'expires_at': expiration_time }
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
92
93
94
    )

    cur.execute("""
95
        INSERT INTO credentials VALUES (CURRVAL('users_user_index_seq'), NULL, %(wsshash)s, %(expires_at)s);
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
96
        """,
97
                {'wsshash': wsshash(login, password), 'expires_at': expiration_time }
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
98
99
    )
    conn.commit()
100
    conn.close()
101
102
103
104
105
106

def register_privileges(login, fdsn_refs):
    """
    - Connect to PRIVILEGEDB
    - For each fdsn reference, insert the privilege in the access table
    """
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
107
108
109
110
111
112
113
    try:
        conn = psycopg2.connect(dbname= application.config['PRIVILEGEDBNAME'],
                            port = application.config['PRIVILEGEDBPORT'],
                            host = application.config['PRIVILEGEDBHOST'],
                            user= application.config['PRIVILEGEDBUSER'],
                            password = application.config['PRIVILEGEDBPASSWORD'])
        cur = conn.cursor()
114
        logging.debug("Connected to privileges database")
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
115
116
117
118
119
120
121
122
123
124
    except Exception as e:
        logging.error("Unable to connect to database %s as %s@%s:%s"%(application.config['PRIVILEGEDBNAME'],
                                                                      application.config['PRIVILEGEDBUSER'],
                                                                      application.config['PRIVILEGEDBHOST'],
                                                                      application.config['PRIVILEGEDBPORT']))
        raise e

    # Get the network id
    for ref in fdsn_refs:
        ref['login'] = login
125
        ref['expires_at'] = datetime.datetime.now()+datetime.timedelta(days=1)
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
126
127
128
129
        cur.execute("""
                    select network_id from networks where start_year=%(startyear)s and end_year=%(endyear)s and network=%(networkcode)s;
                    """, ref)
        ref['networkid'] = cur.fetchone()[0]
130
        logging.info("Inserting tupple in %s.eida_temp_users: %s"%(application.config['PRIVILEGEDBNAME'], ref))
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
131
        cur.execute("""
132
                    insert into eida_temp_users (network_id, network, start_year, end_year, name, expires_at) values (%(networkid)s, %(networkcode)s, %(startyear)s, %(endyear)s, %(login)s, %(expires_at)s);
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
133
134
135
136
                    """, ref)
    conn.commit()
    conn.close()

137

138
139
140
@application.route("/version", methods=['GET'])
def version():
    return Response("Version %s running in %s mode. Contact %s."%(__version__, application.config['ENVIRONMENT'], application.config['EMAIL']), status=200)
141

Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
142
@application.route("/cleanup", methods=['GET'])
143
144
145
146
def cleanup():
    """
    Clean old temporary logins and passwords in both databases.
    """
Jonathan Schaeffer's avatar
typo    
Jonathan Schaeffer committed
147
    logging.info("Cleaning up expired temporary accounts")
148
    rows_deleted = 0
149
150
151
152
153
154
155
156
    try:
        conn = psycopg2.connect(dbname= application.config['AUTHDBNAME'],
                            port = application.config['AUTHDBPORT'],
                            host = application.config['AUTHDBHOST'],
                            user= application.config['AUTHDBUSER'],
                            password = application.config['AUTHDBPASSWORD'])
        cur = conn.cursor()
        logging.debug("Connected to users database")
157
        cur.execute("delete from credentials where expires_at < now();")
158
159
160
161
        cur.execute("delete from users where expires_at < now();")
        rows_deleted = cur.rowcount
        conn.commit()
        conn.close()
162
163
    except psycopg2.Error as e:
        logging.error(e.pgerror)
164
165
        raise e

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
    try:
        conn = psycopg2.connect(dbname= application.config['PRIVILEGEDBNAME'],
                                port = application.config['PRIVILEGEDBPORT'],
                                host = application.config['PRIVILEGEDBHOST'],
                                user= application.config['PRIVILEGEDBUSER'],
                                password = application.config['PRIVILEGEDBPASSWORD'])
        cur = conn.cursor()
        logging.debug("Connected to privlieges database")
        logging.debug("Deleting from privileges database")
        cur.execute("delete from eida_temp_users where expires_at < now();")
        conn.commit()
        conn.close()
    except Exception as e:
        logging.error(e.pgerror)
        raise e
181
    return Response("Deleted %d expired accounts."%(rows_deleted), status=200)
182

183
@application.route("/", methods=['POST'])
184
185
186
def auth():
    login = ''
    password = ''
187
188
189
    logging.debug(request.mimetype)
    data = request.get_data()
    logging.debug("Data: %s", data)
190
    try:
Jonathan Schaeffer's avatar
Jonathan Schaeffer committed
191
        verify_token_signature(data, application.config['GNUPGHOMEDIR'])
192
193
194
195
196
197
198
199
200
201
202
203
204
        tokendict = parse_input_data(data)
        logging.info("Token signature OK: %s"%str(tokendict))
    except ValueError as e:
        logging.info("Token signature could not be checked: %s"%str(data))
        return Response(str(e), status=415)
    # Now we have a dictionary corresponding to the token's content.
    # Verify validity
    expiration_ts=  datetime.datetime.strptime(tokendict['valid_until'], '%Y-%m-%dT%H:%M:%S.%fZ')
    if (expiration_ts - datetime.datetime.now()).total_seconds() < 0:
        logging.info("Token is expired")
        return Response('Token is expired. Please generate a new one at https://geofon.gfz-potsdam.de/eas/', status=415)
    logging.info("Token is valid")

205
206
207
208
209
210
211
212
    # Compute a random login and password
    login = ''.join(random.choices(string.ascii_uppercase + string.digits, k=14))
    password = ''.join(random.choices(string.ascii_uppercase + string.digits, k=14))

    # Register login
    register_login(login, password)

    # Store in PRIVILEGEDB
213
    # Check membership and get FDSN references
214
215
    fdsn_memberships = []
    for em in  tokendict['memberof'].split(';'):
216
        logging.debug("EPOS membership: "+em)
217
        if em in application.config['EPOS_FDSN_MAP']:
218
            logging.debug("   ... is in epos fdsn map")
219
220
            fdsn_memberships.append(application.config['EPOS_FDSN_MAP'][em])

221
222
223
    if len(fdsn_memberships) > 0:
        logging.debug("FDSN memberships: %s"%(fdsn_memberships))
        register_privileges(login, fdsn_memberships)
224

225
    return "%s:%s"%(login, password)
226
227

if __name__ == "__main__":
228
    logging.info("Running in %s mode"%(application.config['ENVIRONMENT']))
229
    application.run(host='0.0.0.0')