Skip to content

Development Notes

alahiff edited this page May 19, 2020 · 11 revisions

A virtual machine has been created at STFC to host the database and run the web server.

Technology

TODO: notes about why technologies we are using.

Installing a development environment

We firstly create a basic Django app then add authentication using GitHub.

Create a simple Django app

This is based on https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-18-04. Information on setting up nginx with SSL is based on https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04.

Firstly install nginx and Certbot, which we will use for obtaining SSL certificates from Let's Encrypt. As root run:

apt update
apt-get install nginx python-certbot-nginx

On some machines ports 80 and 443 are blocked by default by iptables. Run the following commmands as root to open these ports if necessary:

iptables -I INPUT 5 -i ens3 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -I INPUT 5 -i ens3 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT

Note that the network interface above (in this case ens3) may need to be adjusted as appropriate. Use ifconfig to determine the name of the network interface.

Create a server block for your domain in /etc/nginx/sites-available, for example /etc/nginx/sites-available/test.scrc.uk. Example contents:

server {
    server_name test.scrc.uk;

    listen 80 default_server;
    listen [::]:80 default_server;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/ubuntu/scrc;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

In the above the server name has to be adjusted as appropriate and the path for the location of static files needs to be set. Here it is the path to the directory created in a later step.

Create a symbolic link to enable this site in nginx:

ln -s /etc/nginx/sites-available/test.scrc.uk /etc/nginx/sites-enabled/

and start nginx:

systemctl start nginx

Obtain a certificate from Let's Encrypt, e.g.

certbot --nginx -d test.scrc.uk

where the DNS name will have to be adjusted as appropriate.

Install PostgreSQL and some required dependencies:

apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib

Unless otherwise specified, all commands below should be run as a normal user and not as root.

Create a directory for storing the virtual environment and Djanjo project:

mkdir scrc
cd scrc

Create a virtual environment and activate it:

virtualenv dataregistry_env
source dataregistry_env/bin/activate

Install required modules:

pip install django gunicorn psycopg2-binary

Create a data registry project in Django:

django-admin.py startproject dataregistry ~/scrc

Now we setup the PostgreSQL database. Firstly:

$ sudo -u postgres psql
psql (10.12 (Ubuntu 10.12-0ubuntu0.18.04.1))
Type "help" for help.

Create a database:

postgres=# CREATE DATABASE scrc;
CREATE DATABASE

Create a user:

postgres=# CREATE USER scrc WITH PASSWORD 'password';
CREATE ROLE

Give the user the required permissions and exit:

postgres=# ALTER ROLE scrc SET client_encoding TO 'utf8';
ALTER ROLE
postgres=# ALTER ROLE scrc SET default_transaction_isolation TO 'read committed';
ALTER ROLE
postgres=# ALTER ROLE scrc SET timezone TO 'UTC';
ALTER ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE scrc TO scrc;
GRANT
postgres=# \q

Update the file ~/scrc/dataregistry/settings.py to include details about the database. DATABASES should be defined as:

DATABASES = {
        'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'scrc',
        'USER': 'scrc',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
    }
}

Also update ALLOWED_HOSTS as necessary. This should contain localhost in addition to the external DNS name of the host, e.g.

ALLOWED_HOSTS = ['localhost', 'test.scrc.uk']

Finally, at the end of the file ~/scrc/dataregistry/settings.py add the following line:

STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

Run the following commands (make sure you have the virtual environment activated):

~/scrc/manage.py makemigrations
~/scrc/manage.py migrate

Create a superuser account by running:

~/scrc/manage.py createsuperuser

You will be asked to enter a usernamd and password. Then run:

~/scrc/manage.py collectstatic

This ensure all static content is placed in the directory we specifed in ~/scrc/dataregistry/settings.py enabling nginx to handle them correctly.

We now want to run gunicorn as a service. As root, create the file /etc/systemd/system/gunicorn.socket containing:

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

and create the file /etc/systemd/system/gunicorn.service containing:

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=ubuntu
Group=ubuntu
WorkingDirectory=/home/ubuntu/scrc
ExecStart=/home/ubuntu/scrc/dataregistry_env/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          dataregistry.wsgi:application

[Install]
WantedBy=multi-user.target

As root, start the gunicorn socket service. The gunicorn service will automatically be started as necessary.

systemctl start gunicorn.socket
systemctl enable gunicorn.socket

Go to the external URL of the service (e.g. https://test.scrc.uk/) to see the default Django screen.

We now need to create an app within the Django project:

python manage.py startapp dataregistry_app

This creates a directory containing a number of files:

$ tree
.
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py

1 directory, 7 files

Update the file ~/scrc/dataregistry_app/views.py to contain the content:

from django.http import HttpResponse
  
def homePageView(request):
    return HttpResponse('Hello, World!')

Add a file ~/scrc/dataregistry_app/urls.py containing:

from django.urls import path
  
from .views import homePageView

urlpatterns = [
    path('', homePageView, name='home')
]

Now update the file ~/scrc/dataregistry/urls.py to contain:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('dataregistry_app.urls')),
]

After making these changes we need to restart gunicorn for the changes to take affect. As root run:

systemctl restart gunicorn

Now if you go to the external URL of the service (e.g. https://test.scrc.uk/) you should see a page displaying Hello, World!.

Add authentication using GitHub

This is based on https://learndjango.com/tutorials/django-allauth-tutorial.

Setting up django-allauth

Firstly, install the django-allauth package in the virtual environment:

cd ~/scrc ; source dataregistry_env/bin/activate
pip install django-allauth

In the file ~/scrc/dataregistry/settings.py add the following lines to the INSTALLED_APPS list:

    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.github',

At the bottom of the same file add the following lines:

AUTHENTICATION_BACKENDS = (
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
)

SITE_ID = 1

# We don't need email verification upon signup as we're using GitHub
ACCOUNT_EMAIL_VERIFICATION = 'none'

# Redirect authenticated users to this URL
LOGIN_REDIRECT_URL = 'home'

# Specify required scopes
SOCIALACCOUNT_PROVIDERS = {
    'github': {
        'SCOPE': [
            'read:user',
            'user:email',
        ],
    }
}

In the file ~/scrc/dataregistry/urls.py we need to add the URLs provided by the django-allauth packge. The content of this file should now look like:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('allauth.urls')),
    path('', include('dataregistry_app.urls')),
]

Now run migrate to update the database as necessary (make sure you have the virtual environment activated):

python manage.py migrate

Go to GitHub to register an OAuth app. This is explained in https://learndjango.com/tutorials/django-allauth-tutorial. The main two settings are:

For testing this can be done within a normal user account. This will allow anyone with a GitHub account to access the Django app. If you want to restrict access of the Django app to a specific GitHub organization you must register an OAuth app within that organization (this must be done by an adminstrator of the organization). Keep a copy of the Client ID and Client Secret as this will be needed.

Login to the Django admin site, e.g. at https://test.scrc.uk/admin/. Click sites, then click on example.com and change botht the Domain name and Display name to test.scrc.uk. Click Save.

Click on Home at the top left-hand side of the page. Click Social applications and click on ADD SOCIAL APPLICATION. Select GitHub as the Provider. Specify an arbitrary name, for example Github. Set the Client id and Secret key to the Client ID and Client Secret from GitHub, obtained in the previous step. Set test.scrc.uk in the list of Chosen site. Finally click Save.

Updating a page to allow login etc

Now we will create a new home page which will allow unauthenticated users to login.

Create a templates directory:

mkdir ~/scrc/dataregistry_app/templates

then create a file ~/scrc/dataregistry_app/templates/home.html containing:

{% load socialaccount %}

<h1>SCRC Data Registry</h1>
{% if user.is_authenticated %}
<p>Welcome {{ user.username }} !!!</p>
{% else %}
<a href="{% provider_login_url 'github' %}">Sign Up using GitHub</a>
{% endif %}

We need to make Django aware of this template by updating TEMPLATES in ~/scrc/dataregistry/settings.py. In the definition of TEMPLATES replace the DIRS line with:

        'DIRS': [os.path.join(BASE_DIR, 'templates')],

In the same file add the following line to INSTALLED_APPS:

    'dataregistry_app.apps.DataregistryAppConfig',

Update the file ~/scrc/dataregistry_app/urls.py to contain:

from django.urls import path
  
from .views import Home

urlpatterns = [
    path('', Home.as_view(), name='home')
]

and update the file ~/scrc/dataregistry_app/views.py to contain:

from django.views.generic import TemplateView

class Home(TemplateView):
    template_name = 'home.html'

Restart gunicorn by running as root:

systemctl restart gunicorn

Now if you go to https://test.scrc.uk/ you should see a link Sign Up using GitHub. If you click on this you will be redirected to GitHub and be asked to give permission for the Django app to access your email addresses and profile. Once authorized you will be redirected back to Django and should see a message Welcome <GitHub username> !!!.

Preventing authentication via username and password

Currently django-allauth does not have a simple way to prevent users from being able to sign up by creating a username and password. However, there is a workaround. To do this we need to update the file ~/scrc/dataregistry/urls.py to contain:

from django.contrib import admin
from django.urls import path, include
from django.conf.urls import url
from allauth.account.views import confirm_email, login, logout

urlpatterns = [
    url(r'^accounts/email/', login, name='disable_email'),
    url(r'^accounts/password/', login, name='disable_password'),
    url(r'^accounts/inactive/', login, name='disable_inactive'),
    url(r'^accounts/confirm-email/', login, name='disable_confirm_email'),
    path('accounts/', include('allauth.urls')),
    url(r'^login/$', login, name='account_login'),
    url(r'^logout/$', logout, name='account_logout'),
    url(r'^signup/$', login, name='account_signup'),
    path('admin/', admin.site.urls),
    path('', include('dataregistry_app.urls')),
]

This will cause all of the default django-allauth URLs to be directed to the standard login page, preventing users from being able to sign up by creating an account directly in Djando. As usual, gunicorn should be restarted in order for the changes to take effect.

To logout, go to the URL https://test.scrc.uk/logout/.