login

https://gitlab.com/kb/login

Management Commands

login_usernames

To create a CSV file containing the username and full name of all the active users on your site:

django-admin.py login_usernames out.csv

OpenID Connect

Note

If you don’t want to use OpenID Connect, then just set the following in settings/base.py (USE_OPENID_CONNECT = False).

Register your application in the Microsoft Azure Portal.

Add the following to requirements/base.txt:

mozilla-django-oidc==

Tip

See Requirements for the current version…

Add the following to project/urls.py:

url(regex=r"^oidc/", view=include("mozilla_django_oidc.urls")),

Add the following to settings/base.py:

# 17/01/2020, We are getting a redirect loop when we use this 'LOGIN_URL'
# LOGIN_URL = reverse_lazy("oidc_authentication_init")
LOGIN_URL = reverse_lazy("login")

# https://mozilla-django-oidc.readthedocs.io/
USE_OPENID_CONNECT = get_env_variable_bool("USE_OPENID_CONNECT")
AUTHENTICATION_BACKENDS = ("login.service.KBSoftwareOIDCAuthenticationBackend",)
OIDC_CREATE_USER = False
OIDC_OP_AUTHORIZATION_ENDPOINT = get_env_variable(
    "OIDC_OP_AUTHORIZATION_ENDPOINT"
)
OIDC_OP_JWKS_ENDPOINT = get_env_variable("OIDC_OP_JWKS_ENDPOINT")
OIDC_OP_TOKEN_ENDPOINT = get_env_variable("OIDC_OP_TOKEN_ENDPOINT")
OIDC_OP_USER_ENDPOINT = "NOT_USED_BY_KB_LOGIN_SERVICE"
OIDC_RP_CLIENT_ID = get_env_variable("OIDC_RP_CLIENT_ID")
OIDC_RP_CLIENT_SECRET = get_env_variable("OIDC_RP_CLIENT_SECRET")
OIDC_RP_SIGN_ALGO = get_env_variable("OIDC_RP_SIGN_ALGO")
OIDC_USE_NONCE = get_env_variable_bool("OIDC_USE_NONCE")

Warning

Double check your settings files to make sure you don’t have other AUTHENTICATION_BACKENDS configured i.e. The only AUTHENTICATION_BACKENDS in your project should be the one in the section shown above.

Add the following to settings/base.py:

MIDDLEWARE = (
    # ...
    "mozilla_django_oidc.middleware.SessionRefresh",
    "reversion.middleware.RevisionMiddleware",
)

THIRD_PARTY_APPS = (
    "mozilla_django_oidc",

Add the following to settings/local.py:

KB_TEST_EMAIL_FOR_OIDC = get_env_variable("KB_TEST_EMAIL_FOR_OIDC")
KB_TEST_EMAIL_USERNAME = get_env_variable("KB_TEST_EMAIL_USERNAME")

Note

These settings are used by the demo_data_login_oidc management command.

Add the following to .gitlab.ci:

test:
  script:
  - export KB_TEST_EMAIL_FOR_OIDC="patrick@kbsoftware.co.uk"
  - export OIDC_CREATE_USER=False
  - export OIDC_OP_AUTHORIZATION_ENDPOINT="http://localhost:1235/"
  - export OIDC_OP_JWKS_ENDPOINT="http://localhost:1236/"
  - export OIDC_OP_TOKEN_ENDPOINT="http://localhost:1237/"
  - export OIDC_OP_USER_ENDPOINT="http://localhost:1238/"
  - export OIDC_RP_CLIENT_ID="my-oidc-client-id"
  - export OIDC_RP_CLIENT_SECRET="my-oidc-client-secret"
  - export OIDC_RP_SIGN_ALGO="RS256"
  - export OIDC_USE_NONCE=False
  - export USE_OPENID_CONNECT=True

Add the following to your environment e.g. .env.fish:

set -x USE_OPENID_CONNECT "True"

Set-up your .private file using the information from Microsoft Azure.

Testing / Debug

Login will only work if one of your users has a matching email address.

If you have trouble logging a user in, then you can display the email address by adding a print statement to:

venv/lib/python3.6/site-packages/mozilla_django_oidc/auth.py

e.g:

def get_or_create_user(self, access_token, id_token, payload):
    user_info = self.get_userinfo(access_token, id_token, payload)
    email = user_info.get("email")
    print(email)

When testing (on your laptop) you can use the demo_data_login_oidc management command to update the email address for the staff user:

  1. Use .env.fish to set the KB_TEST_EMAIL_FOR_OIDC and KB_TEST_EMAIL_USERNAME environment variables. This will need to match the email address of the user in the Azure portal.

  2. Run the django-admin.py demo_data_login_oidc management command to update the email address for the KB_TEST_EMAIL_USERNAME user to match the KB_TEST_EMAIL_FOR_OIDC environment variable:

Deployment

Using the information from your .private file (see above and Microsoft Azure for more information), update the Salt pillar file for your site e.g:

sites:
  my_site:
    package: bpm
    profile: django
    env:
      use_openid_connect: True
      oidc_op_authorization_endpoint: "https://login.microsoftonline.com/fcee251/oauth2/v2.0/authorize"
      oidc_op_jwks_endpoint: "https://login.microsoftonline.com/common/discovery/v2.0/keys"
      oidc_op_token_endpoint: "https://login.microsoftonline.com/fcee251/oauth2/v2.0/token"
      oidc_rp_client_id: "36ad9"
      oidc_rp_client_secret: "aead6"
      oidc_rp_sign_algo: "RS256"
      oidc_use_nonce: False

Password

Brute Force

django-axes will lock out repeated attempts from the same IP address.

To configure:

Add django-axes to requirements/base.txt

Add axes to THIRD_PARTY_APPS in settings/base.py:

THIRD_PARTY_APPS = (
    'axes',

Configure in settings/base.py:

from datetime import timedelta
AXES_COOLOFF_TIME = timedelta(minutes=15)
AXES_FAILURE_LIMIT = 5
AXES_LOCKOUT_TEMPLATE = 'login/axes_lockout_template.html',
AXES_PASSWORD_FORM_FIELD = 'password1'

Note

AXES_COOLOFF_TIME configures a 15 minute cooling off period before the next login attempt can be made.

Note

The axes_lockout_template.html is in the login app.

To administer Axes, the admin app has a list of Access attempts and Access logs at /admin/axes/.

Reset all lockouts and access records:

django-admin.py axes_reset

Clear lockout/records for an ip address:

django-admin.py axes_reset ip

Reset

When a user (or non-user) attemps to reset their password, the Notify users are emailed. For logic, see: https://gitlab.com/kb/login/blob/master/login/forms.py

I think this is a potential risk for a DOS attack. If we get a DOS attack then we could use the PasswordResetAudit model to limit the number of notification emails we send: https://gitlab.com/kb/login/blob/master/login/models.py

To see an audit of password reset attempts, browse to /accounts/password/reset/audit/report/

Validation

Tip

Unit tests for this feature are in the login app.

To add password validators, just add them to this list in settings/base.py.

We have these validators working on a live project:

AUTH_PASSWORD_VALIDATORS = [
    {
       'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 8,
        }
    },
]

For documentation, see Enabling password validation

Register

If you want to allow users to register on your site, add the following to urls.py:

from login.views import RegisterCreateView

url(regex=r'^accounts/register/$',
    view=RegisterCreateView.as_view(),
    name='register'
    ),

Staff

If you want a member of staff to be able to update user names and passwords for other users:

Create a couple of views in views.py. This will allow you to set the success URL for your project:

from login.views import (
    UpdateUserNameView,
    UpdateUserPasswordView,
)

class MyUpdateUserNameView(UpdateUserNameView):

    def get_success_url(self):
        return reverse('example.test')

class MyUpdateUserPasswordView(UpdateUserPasswordView):

    def get_success_url(self):
        return reverse('example.test')

Add the views to urls.py:

from .views import (
    MyUpdateUserNameView,
    MyUpdateUserPasswordView,
)

url(regex=r'^accounts/user/(?P<pk>\d+)/username/$',
    view=MyUpdateUserNameView.as_view(),
    name='update_user_name',
    ),
url(regex=r'^accounts/user/(?P<pk>\d+)/password/$',
    view=MyUpdateUserPasswordView.as_view(),
    name='update_user_password',
    ),

You can use these views in your project as follows:

<td>
  <a href="{% url 'update_user_name' u.pk %}">
    <i class="fa fa-edit"></i>
    {{ u.username }}
  </a>
</td>
<td>
  <a href="{% url 'update_user_password' u.pk %}">
    <i class="fa fa-edit"></i>
    ********
  </a>
</td>