diff --git a/README.md b/README.md index 12053c4..0bb74c8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ORCID middleware to enable our researchers to designate GW as a trusted partner 1. Create your secure key and certificate for SAML encryption/decryption: `openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout sp.key` - These files should go into an `orcidflask/saml/certs` directory. 2. In the `orcidflask/saml` directory, create a `settings.json` file to provide the metadata for your app and your identity provider, as well as the certificate from your identify provider. You can follow the example on the [python3-saml repository](https://github.com/onelogin/python3-saml) or in `example-settings.json`. -3. Copy the example Flask configuration file and edit it to provide sensitive keys, including the SERVER_KEY, ORCID client ID and ORCID client secret. +3. Copy the example Flask configuration file and edit it to provide sensitive keys, including the SERVER_KEY, ORCID client ID and ORCID client secret. The `SERVER_KEY` should be the key used to encrypt the Flask session objects, as described [here](https://flask.palletsprojects.com/en/2.2.x/config/). `cp example.config.py config.py` 4. Copy `example.docker-compose.yml` to `docker-compose.yml` and `example.env` to `.env`. 5. Bring up the Docker container(s): `docker-compose up -d`. This will install all necessary dependencies and launch the Flask app with gunicorn on port `8080`. For development, comment out the first three lines under the `volumes` section of the `flask-app` service and uncomment the line `.:/opt/orcid_integration`. This will use the local copy of the Python code. @@ -49,3 +49,7 @@ ORCID middleware to enable our researchers to designate GW as a trusted partner proxy_redirect off; } }``` +10. To quickly serialize the database as a JSON file, you can run the following command (if outside the container), providing the path to a file in a mounted volume: + ``` + docker exec -it orcid-integration_flask-app_1 flask serialize-db ./data/token-dump.json + ``` \ No newline at end of file diff --git a/example.config.py b/example.config.py index 439524e..d7169be 100644 --- a/example.config.py +++ b/example.config.py @@ -1,6 +1,9 @@ SECRET_KEY = b'' CLIENT_ID = '' CLIENT_SECRET = '' +SERVER_NAME = '' SLO_REDIRECT = '' ORCID_SUCCESS_URL = '' -ORCID_FAILURE_URL = '' \ No newline at end of file +ORCID_FAILURE_URL = '' +# Set to true to prepopulate ORCID's registration for with values from the SAML IdP +PREFILL_REGISTRATION = False \ No newline at end of file diff --git a/example.docker-compose.yml b/example.docker-compose.yml index c07a696..7a77f6f 100644 --- a/example.docker-compose.yml +++ b/example.docker-compose.yml @@ -23,8 +23,9 @@ services: - POSTGRES_DB_HOST=${POSTGRES_DB_HOST} - POSTGRES_PORT=${POSTGRES_PORT} - DB_ENCRYPTION_FILE=${DB_ENCRYPTION_FILE} + - ORCID_SERVER volumes: - #- ./orcidflask/saml:/opt/orcid_integration/orcidflask/saml - #- ./config.py:/opt/orcid_integration/config.py - #- ./orcidflask/db:/opt/orcid_integration/orcidflask/db - - .:/opt/orcid_integration + - ./orcidflask/saml:/opt/orcid_integration/orcidflask/saml + - ./config.py:/opt/orcid_integration/config.py + - ./orcidflask/db:/opt/orcid_integration/orcidflask/db + #- .:/opt/orcid_integration diff --git a/example.env b/example.env index a4744e8..8ba6d07 100644 --- a/example.env +++ b/example.env @@ -4,3 +4,5 @@ POSTGRES_DB=orcidig POSTGRES_DB_HOST=db POSTGRES_PORT=5432 DB_ENCRYPTION_FILE=/opt/orcid_integration/orcidflask/db/db-encrypt.key +# Values are sandbox or prod +ORCID_SERVER=sandbox diff --git a/orcid_utils.py b/orcid_utils.py index 2cd7ad1..61f7072 100644 --- a/orcid_utils.py +++ b/orcid_utils.py @@ -13,14 +13,15 @@ def prepare_token_payload(code: str): 'code': code, 'redirect_uri': url_for('orcid_redirect', _external=True, _scheme='https')} -def extract_saml_user_data(session): +def extract_saml_user_data(session, populate=True): ''' Extracts name and email attributes from the samlUserData object. :param session: a Flask session object + :param populate: set to False to turn off this feature (returns attributes mapped to empty strings) ''' saml_attrs = ['emailaddress', 'firstname', 'lastname'] saml_data = {} - if 'samlUserdata' in session: + if 'samlUserdata' in session and populate: user_data = session['samlUserdata'] for saml_attr in saml_attrs: value = user_data.get(saml_attr) @@ -28,6 +29,8 @@ def extract_saml_user_data(session): if value: value = value[0] saml_data[saml_attr] = value + else: + saml_data = {s: '' for s in saml_attrs} return saml_data def new_encryption_key(file, replace=False): diff --git a/orcidflask/__init__.py b/orcidflask/__init__.py index 373244f..c6b14b8 100644 --- a/orcidflask/__init__.py +++ b/orcidflask/__init__.py @@ -3,15 +3,22 @@ import os import click from orcid_utils import load_encryption_key, new_encryption_key +import json app = Flask(__name__) # load default configs from default_settings.py app.config.from_object('orcidflask.default_settings') # load sensitive config settings app.config.from_envvar('ORCIDFLASK_SETTINGS') +# Set the ORCID URL based on the setting in default_settings.py +if os.getenv('ORCID_SERVER') == 'sandbox': + base_url = 'https://sandbox.orcid.org' +else: + base_url = 'https://orcid.org' # Personal attributes from SAML metadata definitions -app.config['orcid_auth_url'] = 'https://sandbox.orcid.org/oauth/authorize?client_id={orcid_client_id}&response_type=code&scope={scopes}&redirect_uri={redirect_uri}&family_names={lastname}&given_names={firstname}&email={emailaddress}' -app.config['orcid_token_url'] = 'https://sandbox.orcid.org/oauth/token' +app.config['orcid_auth_url'] = base_url + '/oauth/authorize?client_id={orcid_client_id}&response_type=code&scope={scopes}&redirect_uri={redirect_uri}&family_names={lastname}&given_names={firstname}&email={emailaddress}' +app.config['orcid_register_url'] = base_url + '/register?client_id={orcid_client_id}&response_type=code&scope={scopes}&redirect_uri={redirect_uri}&family_names={lastname}&given_names={firstname}&email={emailaddress}' +app.config['orcid_token_url'] = base_url + '/oauth/token' app.config['SAML_PATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'saml') app.secret_key = app.config['SECRET_KEY'] @@ -28,6 +35,7 @@ db.create_all() import orcidflask.views +from orcidflask.models import Token @app.cli.command('create-secret-key') @click.argument('file') @@ -42,4 +50,17 @@ def reset_db(): ''' Resets the associated database by dropping all tables. Warning: for development purposes only. Do not run on a production instance without first backing up the database, as this command will result in the loss of all data. ''' - db.drop_all() \ No newline at end of file + db.drop_all() + +@app.cli.command('serialize-db') +@click.argument('file', type=click.File('w')) +def serialize_db(file): + ''' + Serializes the database as a JSON dump. Argument should be the path to a file, preferably in a volume mapped to the container, such as /opt/orcid_integration/data + ''' + # get all records from the database + records = Token.query.all() + # convert to Python dicts + records = [record.to_dict() for record in records] + json.dump(records, file) + \ No newline at end of file diff --git a/orcidflask/models.py b/orcidflask/models.py index 04a36bc..eeabb00 100644 --- a/orcidflask/models.py +++ b/orcidflask/models.py @@ -41,3 +41,13 @@ class Token(db.Model): def __repr__(self): return '