Skip to content

Commit

Permalink
Refactor support for database URIs in auth tool (asreview#1602)
Browse files Browse the repository at this point in the history
  • Loading branch information
cskaandorp authored Dec 5, 2023
1 parent ed4dd95 commit 1c704b1
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 94 deletions.
20 changes: 8 additions & 12 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,6 @@ follow the guidelines below.









## Authentication

It is possible to run ASReview with authentication, enabling multiple users to run their
Expand All @@ -207,7 +201,7 @@ database (asreview.development.sqlite or asreview.production.sqlite) in the ASRe
folder to store that information.

Note that it is possible to run the authenticated application with a
[Postgresql database](https://www.postgresql.org/). Using Postgresql requires 2 extra
[PostgreSQL database](https://www.postgresql.org/). Using Postgresql requires 2 extra
installation steps:
1. Install the [psycopg2](https://www.psycopg.org/docs/) package. At the time of this writing
2 versions of this package exist: `psycopg2` and `psycopg2-binary`. According to the
Expand Down Expand Up @@ -323,18 +317,20 @@ Under the CLI sub commands of the ASReview application a tool can be found that
$ asreview auth-tool --help
```

All auth-tool sub commands, except for `list-projects`, __require a database uri__. The uri can be passed with the `--db-uri` option. If none is provided, the tool looks for an environment variable `SQLALCHEMY_DATABASE_URI`. If that environment variable can't be found either, the tool defaults to a uri of a SQLite database stored in the ASReview folder with filename `asreview.production.sqlite`.

#### Creating user accounts

The first step is to create user accounts. This can be done interactively or by using a JSON string to bulk insert the accounts. To add user accounts interactively run the following command:
```
$ asreview auth-tool add-users --db-path ~/.asreview/asreview.production.sqlite
$ asreview auth-tool add-users
```

Note that the absolute path of the sqlite database has to be provided. Also note that if your app runs in development mode, use the `asreview.development.sqlite` database instead. The tool will prompt you if you would like to add a user account. Type `Y` to continue and enter an email address, name, affiliation (not required) and a password for every person. Continue to add as many users as you would like.

If you would like to bulk insert user accounts use the `--json` option:
```
$ asreview auth-tool add-users -j "[{\"email\": \"[email protected]\", \"name\": \"Name of User\", \"affiliation\": \"Some Place\", \"password\": \"1234@ABcd\"}]" --db-path ~/.asreview/asreview.production.sqlite
$ asreview auth-tool add-users -j "[{\"email\": \"[email protected]\", \"name\": \"Name of User\", \"affiliation\": \"Some Place\", \"password\": \"1234@ABcd\"}]"
```
The JSON string represents a Python list with a dictionary for every user account with the following keys: `email`, `name`, `affiliation` and `password`. Note that passwords require at least one symbol. These symbols, such as the exclamation mark, may compromise the integrity of the JSON string.

Expand All @@ -353,20 +349,20 @@ $ asreview auth-tool list-projects --json
the tool returns a convenient JSON string that can be used to bulk insert and link projects into the database. The string represents a Python list containing a dictionary for every project. Since the ID of the user account of
the owner is initially unknown, the `0` behind every `owner_id` key needs to be replaced with the appropriate owner ID. That ID number can be found if we list all user accounts with the following command:
```
$ asreview auth-tool list-users --db-path ~/.asreview/asreview.production.sqlite
$ asreview auth-tool list-users
```

#### Inserting and linking the projects into the database

Inserting and linking the projects into the database can be done interactively:
```
$ asreview auth-tool link-projects --db-path ~/.asreview/asreview.production.sqlite
$ asreview auth-tool link-projects
```
The tool will list project by project and asks what the ID of the owner is. That ID can be found in the user list below the project information.

One can also insert all project information by using the JSON string that was produced in the previous step:
```
$ asreview auth-tool link-projects --json "[{\"folder\": \"project-id\", \"version\": \"1.1+51.g0ebdb0c.dirty\", \"project_id\": \"project-id\", \"name\": \"project 1\", \"authors\": \"Authors\", \"created\": \"2023-04-12 21:23:28.625859\", \"owner_id\": 15}]" --db-path ~/.asreview/asreview.production.sqlite
$ asreview auth-tool link-projects --json "[{\"folder\": \"project-id\", \"version\": \"1.1+51.g0ebdb0c.dirty\", \"project_id\": \"project-id\", \"name\": \"project 1\", \"authors\": \"Authors\", \"created\": \"2023-04-12 21:23:28.625859\", \"owner_id\": 15}]"
```


Expand Down
116 changes: 34 additions & 82 deletions asreview/entry_points/auth_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

import argparse
import json
import os
import sys
from argparse import RawTextHelpFormatter
from uuid import UUID

from sqlalchemy import create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import create_database
from sqlalchemy_utils import database_exists

from asreview.entry_points.base import BaseEntryPoint
from asreview.project import ASReviewProject
Expand All @@ -19,6 +18,9 @@
from asreview.webapp.authentication.models import User
from asreview.webapp.authentication.models import create_database_and_tables

DEFAULT_DATABASE_URI = \
f"sqlite:///{str(asreview_path())}/asreview.production.sqlite"


def auth_parser():
parser = argparse.ArgumentParser(
Expand All @@ -34,75 +36,32 @@ def auth_parser():

sub_parser = parser.add_subparsers(help="The following options are available:")

# CREATE DB
create_db_par = sub_parser.add_parser(
"create-db",
help="Create the database necessary to authenticate the ASReview app."
)

subparser = create_db_par.add_subparsers(dest="db_type", required=True)

sqlite = subparser.add_parser("sqlite3")
sqlite.add_argument(
"-p",
"--db-path",
type=str,
help="Absolute path of folder where Sqlite3 database must be created",
required=True
)
sqlite.add_argument(
"-n",
"--db-name",
create_db_par.add_argument(
"-d",
"--db-uri",
type=str,
default="asreview.sqlite3",
help="Name of the Sqlite3 database (used as filename)",
required=True
default=None,
help="URI of database"
)

postgres = subparser.add_parser("postgresql")
postgres.add_argument(
"-n",
"--db-name",
type=str,
help="Name of the PostgreSQL database",
default="asreview",
required=True,
)
postgres.add_argument(
"-u",
"--username",
type=str,
default="root",
help="User name if PostgreSQL requires authentication",
# ADD USERS
user_par = sub_parser.add_parser(
"add-users",
help="Add users into the database."
)
postgres.add_argument(
"-p",
"--password",
type=str,
help="Password if PostgreSQL requires authentication",
)
postgres.add_argument(
"-H",
"--host",
type=str,
help="Host URL of database",
default="127.0.0.1",
)
postgres.add_argument(
"-P",
"--port",
type=int,
help="Port of database",
default=5432,
)

user_par = sub_parser.add_parser("add-users", help="Add users into the database.")

user_par.add_argument(
"-d",
"--db-uri",
type=str,
help="Absolute path to authentication sqlite3 database.",
required=True,
default=None,
help="URI of database"
)

user_par.add_argument(
Expand All @@ -112,6 +71,7 @@ def auth_parser():
help="JSON string that contains a list with user account data.",
)

# LIST USERS
list_users_par = sub_parser.add_parser(
"list-users",
help="List user accounts.",
Expand All @@ -121,21 +81,24 @@ def auth_parser():
"-d",
"--db-uri",
type=str,
help="Absolute path to authentication sqlite3 database.",
required=True,
default=None,
help="URI of database"
)

# LIST PROJECTS
list_projects_par = sub_parser.add_parser(
"list-projects",
help="List project info from all projects in the ASReview folder.",
)

list_projects_par.add_argument(
"-j",
"--json",
action="store_true",
help="Create JSON string to connect existing projects with users.",
)

# LINK PROJECTS TO USERS
link_par = sub_parser.add_parser(
"link-projects", help="Link projects to user accounts."
)
Expand All @@ -151,8 +114,8 @@ def auth_parser():
"-d",
"--db-uri",
type=str,
help="Absolute path to authentication sqlite3 database.",
required=True,
default=None,
help="URI of database"
)

return parser
Expand Down Expand Up @@ -223,10 +186,15 @@ def execute(self, argv):
self.args = args
self.argv = argv

# create a conn object for the database
if hasattr(self.args, "db_uri") and self.args.db_uri is not None:
# create a conn object for the database if needed (database
# is not needed when the only command is "list-projects", all
# other commands need a database session)
if self.argv != ["list-projects"]:
self.uri = getattr(self.args, "db_uri", False) or \
os.environ.get("SQLALCHEMY_DATABASE_URI", False) or \
DEFAULT_DATABASE_URI
Session = sessionmaker()
engine = create_engine(self.args.db_uri)
engine = create_engine(self.uri)
Session.configure(bind=engine)
self.session = Session()

Expand All @@ -242,25 +210,9 @@ def execute(self, argv):
self.link_projects()

def create_database(self):
if self.args.db_type == "sqlite3":
uri = f"sqlite:///{self.args.db_path}/{self.args.db_name}"
elif self.args.db_type == "postgresql":
username = self.args.username
host = self.args.host
port = self.args.port
db_name = self.args.db_name
password = f":{self.args.password}" if self.args.password else ""
uri = f"postgresql+psycopg2://{username}{password}@{host}:{port}/{db_name}"

# create the database if necessary
if not database_exists(uri):
create_database(uri)
else:
# open for more database types
uri = ""
engine = create_engine(uri)
engine = create_engine(self.uri)
create_database_and_tables(engine)
print(f"...Database created, URI: {uri}")
print(f"...Database created, URI: {self.uri}")

def add_users(self):
if self.args.json is not None:
Expand Down

0 comments on commit 1c704b1

Please sign in to comment.