mirror of
https://github.com/Forceu/Gokapi.git
synced 2026-01-06 00:49:33 -06:00
Added wildcard for OIDC groups and users, better login flow for OIDC, updated documentation to add OIDC provider examples
This commit is contained in:
@@ -57,34 +57,34 @@ Available environment variables
|
||||
==================================
|
||||
|
||||
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| Name | Action | Persistent* | Default |
|
||||
+==========================+==============================================================================+=============+=============================+
|
||||
| GOKAPI_CONFIG_DIR | Sets the directory for the config file | No | config |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| GOKAPI_CONFIG_FILE | Sets the name of the config file | No | config.json |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| GOKAPI_DATA_DIR | Sets the directory for the data | Yes | data |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| GOKAPI_DB_NAME | Sets the name for the database file | No | gokapi.sqlite |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| GOKAPI_LENGTH_ID | Sets the length of the download IDs. Value needs to be 5 or more | Yes | 15 |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| GOKAPI_MAX_FILESIZE | Sets the maximum allowed file size in MB | Yes | 102400 (100GB) |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| GOKAPI_MAX_MEMORY_UPLOAD | Sets the amount of RAM in MB that can be allocated for an upload. | Yes | 20 |
|
||||
| | | | |
|
||||
| | Any upload with a size greater than that will be written to a temporary file | | |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| GOKAPI_PORT | Sets the webserver port | Yes | 53842 |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
| TMPDIR | Sets the path which contains temporary files | No | Non-Docker: Default OS path |
|
||||
| | | | |
|
||||
| | | | Docker: [DATA_DIR] |
|
||||
+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| Name | Action | Persistent [*]_ | Default |
|
||||
+==========================+==============================================================================+=================+=============================+
|
||||
| GOKAPI_CONFIG_DIR | Sets the directory for the config file | No | config |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| GOKAPI_CONFIG_FILE | Sets the name of the config file | No | config.json |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| GOKAPI_DATA_DIR | Sets the directory for the data | Yes | data |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| GOKAPI_DB_NAME | Sets the name for the database file | No | gokapi.sqlite |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| GOKAPI_LENGTH_ID | Sets the length of the download IDs. Value needs to be 5 or more | Yes | 15 |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| GOKAPI_MAX_FILESIZE | Sets the maximum allowed file size in MB | Yes | 102400 (100GB) |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| GOKAPI_MAX_MEMORY_UPLOAD | Sets the amount of RAM in MB that can be allocated for an upload. | Yes | 20 |
|
||||
| | | | |
|
||||
| | Any upload with a size greater than that will be written to a temporary file | | |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| GOKAPI_PORT | Sets the webserver port | Yes | 53842 |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
| TMPDIR | Sets the path which contains temporary files | No | Non-Docker: Default OS path |
|
||||
| | | | |
|
||||
| | | | Docker: [DATA_DIR] |
|
||||
+--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
|
||||
|
||||
|
||||
\* Variables that are persistent must be submitted during the first start when Gokapi creates a new config file. They can be omitted afterwards. Non-persistent variables need to be set on every start.
|
||||
.. [*] Variables that are persistent must be submitted during the first start when Gokapi creates a new config file. They can be omitted afterwards. Non-persistent variables need to be set on every start.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,3 +57,7 @@ html_static_path = ['']
|
||||
|
||||
master_doc = 'index'
|
||||
#autosectionlabel_prefix_document = True
|
||||
|
||||
html_theme_options = {
|
||||
'navigation_depth': 5,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.. _contributions:
|
||||
|
||||
|
||||
=============
|
||||
Contributions
|
||||
=============
|
||||
|
||||
|
||||
260
docs/examples.rst
Normal file
260
docs/examples.rst
Normal file
@@ -0,0 +1,260 @@
|
||||
.. _examples:
|
||||
|
||||
|
||||
===========================
|
||||
Examples
|
||||
===========================
|
||||
|
||||
|
||||
*********************************
|
||||
OpenID Connect Configuration
|
||||
*********************************
|
||||
|
||||
|
||||
.. _oidcconfig_authelia:
|
||||
|
||||
Authelia
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Server Configuration
|
||||
""""""""""""""""""""""
|
||||
|
||||
.. note::
|
||||
This guide has been written for version 4.37.5
|
||||
|
||||
See the `Authelia documentation <https://www.authelia.com/configuration/identity-providers/open-id-connect/>`_ on how to setup an OIDC server. An example file would be as followed:
|
||||
|
||||
|
||||
.. code-block:: YAML
|
||||
|
||||
identity_providers:
|
||||
oidc:
|
||||
hmac_secret: noz1Aow6Soo9lieyus2E_EXAMPLE_KEY
|
||||
issuer_private_key: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
ohf2shae1bahph7ahSh1
|
||||
EXAMPLE_KEY
|
||||
EP3EihoPhei9iingai0v==
|
||||
-----END PRIVATE KEY-----
|
||||
access_token_lifespan: 1h
|
||||
authorize_code_lifespan: 1m
|
||||
id_token_lifespan: 1h
|
||||
refresh_token_lifespan: 90m
|
||||
enable_client_debug_messages: false
|
||||
enforce_pkce: public_clients_only
|
||||
cors:
|
||||
endpoints:
|
||||
- authorization
|
||||
- token
|
||||
- revocation
|
||||
- introspection
|
||||
allowed_origins:
|
||||
- "https://*.your.domain"
|
||||
allowed_origins_from_client_redirect_uris: false
|
||||
clients:
|
||||
- id: gokapi-dev
|
||||
description: Gokapi Example
|
||||
secret: 'AhXeV7_EXAMPLE_KEY'
|
||||
sector_identifier: ''
|
||||
public: false
|
||||
authorization_policy: one_factor
|
||||
consent_mode: pre-configured
|
||||
pre_configured_consent_duration: 1w
|
||||
audience: []
|
||||
scopes:
|
||||
- openid
|
||||
- email
|
||||
- profile
|
||||
- groups
|
||||
redirect_uris:
|
||||
- https://gokapi.website.com/oauth-callback
|
||||
userinfo_signing_algorithm: none
|
||||
|
||||
|
||||
* Set ``authorization_policy`` to ``two_factor`` to use OTP or a hardware key.
|
||||
* If ``consent_mode`` is ``pre-configured``, the user has the option to remember consent. That way you can use a lower ``Recheck identity`` interval in Gokapi. Logout through the Gokapi interface will not be possible anymore, unless the user logs out their Authelia account. If the option is set to ``explicit``, the user always has to grant the permission aftter the ``Recheck identity`` interval has passed
|
||||
* ``scopes`` may exclude ``email`` and ``groups`` if these are not required for authentication, e.g. if all users registered with Authelia may access Gokapi.
|
||||
* Make sure ``redirect_uris`` is set to the correct value
|
||||
|
||||
|
||||
Gokapi Configuration
|
||||
""""""""""""""""""""""
|
||||
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Gokapi Configuration | Input | Example |
|
||||
+==========================+===========================================================+=========================================+
|
||||
| Provider URL | URL to Authelia Server | \https://auth.autheliaserver.com |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Client ID | Client ID provided in config | gokapi-dev |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Client Secret | Client secret provided in config | AhXeV7_EXAMPLE_KEY |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Recheck identity | If mode is ``pre-configured``, use a low interval. | 12 hours |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Restrict to user | Check this if only certain users shall be allowed to | checked |
|
||||
| | | |
|
||||
| | access Gokapi admin menu | |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Scope identifier (user) | Use a scope that is unique to the user, e.g. the username | email |
|
||||
| | | |
|
||||
| | or the email | |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Authorised users | Enter all users, separated by semicolon | \*\@company.com;admin\@othercompany.com |
|
||||
| | | |
|
||||
| | ``*`` can be used as a wildcard | |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Restrict to group | Check this if only users from certain groups shall be | checked |
|
||||
| | | |
|
||||
| | allowed to access Gokapi admin menu | |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Scope identifier (group) | Use a scope that lists the user's groups | groups |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
| Authorised groups | Enter all groups, separated by semicolon | dev;admins;gokapi-* |
|
||||
| | | |
|
||||
| | ``*`` can be used as a wildcard | |
|
||||
+--------------------------+-----------------------------------------------------------+-----------------------------------------+
|
||||
|
||||
|
||||
.. _oidcconfig_keycloak:
|
||||
|
||||
Keycloak
|
||||
^^^^^^^^^^^^
|
||||
|
||||
.. note::
|
||||
This guide has been written for version 23.0.4
|
||||
|
||||
|
||||
Server Configuration
|
||||
""""""""""""""""""""""
|
||||
|
||||
|
||||
Creating the client
|
||||
**********************
|
||||
|
||||
#. In your realm (default: master) click on ``[Manage] Clients`` and then ``Create Client``
|
||||
|
||||
* Client Type: OpenID Connect
|
||||
* Client ID: a unique ID, ``gokapi-dev`` is used in this example
|
||||
#. Click ``Next``
|
||||
|
||||
* Set ``Client authentication`` to on
|
||||
* Only select ``Standard flow`` in ``Authentication flow``
|
||||
#. Click ``Next``
|
||||
|
||||
* Add your redirect URL, e.g. ``https://gokapi.website.com/oauth-callback``
|
||||
* Click ``Save``
|
||||
#. Click ``Credentials``
|
||||
|
||||
* Note the ``Client Secret``
|
||||
|
||||
|
||||
Addding a scope for exposing groups (optional)
|
||||
*****************************************************
|
||||
|
||||
#. In the realm click on ``[Manage] Client Scopes`` and then ``Create Scope``
|
||||
|
||||
* Name: groups
|
||||
* Type: Optional
|
||||
* Protocol: OpenID Connect
|
||||
* Click ``Save``
|
||||
|
||||
#. Click ``Mappers``
|
||||
|
||||
* Click ``Add predefined mapper``
|
||||
* Search for ``groups`` and tick
|
||||
* Click ``Add``
|
||||
#. In the realm click on ``[Manage] Clients`` and then ``gokapi-dev``
|
||||
* Click ``Client Scopes``
|
||||
* Click ``Add Client Scope``
|
||||
* Select ``groups`` and click ``Add / Optional``
|
||||
|
||||
|
||||
Gokapi Configuration
|
||||
""""""""""""""""""""""
|
||||
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Gokapi Configuration | Input | Example |
|
||||
+==========================+===========================================================+============================================+
|
||||
| Provider URL | URL to Keycloak realm | \http://keycloak.server.com/realms/master/ |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Client ID | Client ID provided | gokapi-dev |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Client Secret | Client secret provided | AhXeV7_EXAMPLE_KEY |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Recheck identity | If mode is ``pre-configured``, use a low interval. | 12 hours |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Restrict to user | Check this if only certain users shall be allowed to | checked |
|
||||
| | | |
|
||||
| | access Gokapi admin menu | |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Scope identifier (user) | Use a scope that is unique to the user, e.g. the username | email |
|
||||
| | | |
|
||||
| | or the email | |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Authorised users | Enter all users, separated by semicolon | \*\@company.com;admin\@othercompany.com |
|
||||
| | | |
|
||||
| | ``*`` can be used as a wildcard | |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Restrict to group | Check this if only users from certain groups shall be | checked |
|
||||
| | | |
|
||||
| | allowed to access Gokapi admin menu | |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Scope identifier (group) | Use a scope that lists the user's groups | groups |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
| Authorised groups | Enter all groups, separated by semicolon | dev;admins;gokapi-* |
|
||||
| | | |
|
||||
| | ``*`` can be used as a wildcard | |
|
||||
+--------------------------+-----------------------------------------------------------+--------------------------------------------+
|
||||
|
||||
|
||||
.. note::
|
||||
Logout through the Gokapi interface will not be possible anymore, unless the user logs out their Keycload account.
|
||||
|
||||
|
||||
|
||||
.. _oidcconfig_google:
|
||||
|
||||
Google
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Server Configuration
|
||||
""""""""""""""""""""""
|
||||
|
||||
.. note::
|
||||
This guide has been last updated in January 2024 and is based on `this documentation <https://support.google.com/cloud/answer/6158849>`_
|
||||
|
||||
#. Go to the `Google Cloud Platform Console <https://console.cloud.google.com/>`_.
|
||||
#. From the projects list, select a project or create a new one.
|
||||
#. If the APIs & services page isn't already open, open the console left side menu and select APIs & services.
|
||||
#. On the left, click Credentials.
|
||||
#. Click New Credentials, then select OAuth client ID.
|
||||
#. Select Application Type ``Webapplication``
|
||||
#. Add the correct Gokapi redirect URL and click Create
|
||||
|
||||
|
||||
Gokapi Configuration
|
||||
""""""""""""""""""""""
|
||||
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
| Gokapi Configuration | Input | Example |
|
||||
+=========================+==================================================+==================================+
|
||||
| Provider URL | \https://accounts.google.com | \https://accounts.google.com |
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
| Client ID | Client ID provided | XXX.apps.googleusercontent.com |
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
| Client Secret | Client secret provided | AhXeV7_EXAMPLE_KEY |
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
| Recheck identity | Use a low interval. | 12 hours |
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
| Restrict to user | Check this, otherwise any Google user can access | checked |
|
||||
| | | |
|
||||
| | | |
|
||||
| | your Gokapi admin menu | |
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
| Scope identifier (user) | email | email |
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
| Authorised users | Enter all users, separated by semicolon | user\@gmail.com;admin\@gmail.com |
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
| Restrict to group | Unsupported | unchecked |
|
||||
+-------------------------+--------------------------------------------------+----------------------------------+
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.. _index:
|
||||
|
||||
===========================
|
||||
Gokapi
|
||||
===========================
|
||||
|
||||
@@ -14,11 +15,12 @@ Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 2
|
||||
|
||||
setup
|
||||
usage
|
||||
update
|
||||
advanced
|
||||
contributions
|
||||
changelog
|
||||
setup
|
||||
usage
|
||||
update
|
||||
advanced
|
||||
examples
|
||||
contributions
|
||||
changelog
|
||||
|
||||
@@ -110,30 +110,78 @@ The default authentication method. A single admin user will be generated that au
|
||||
OAuth2 OpenID Connect
|
||||
************************
|
||||
|
||||
Use this to authenticate with an OIDC server, eg. Google, Github or an internal server. *Note:* If a user is revoked on the OIDC server, it might take several days to affect the Gokapi session.
|
||||
Setup interface
|
||||
========================
|
||||
|
||||
+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
|
||||
| Option | Expected Entry | Example |
|
||||
+===============+=================================================================================+=============================================+
|
||||
| Provider URL | The URL to connect to the OIDC server | https://accounts.google.com |
|
||||
+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
|
||||
| Client ID | Client ID provided by the OIDC server | [random String] |
|
||||
+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
|
||||
| Client Secret | Client secret provided by the OIDC server | [random String] |
|
||||
+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
|
||||
| Allowed users | List of users that is allowed to log in as an admin. | gokapiuser@gmail.com;companyadmin@gmail.com |
|
||||
| | Separate users with a semicolon or leave blank to allow any authenticated user | |
|
||||
+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
|
||||
Use this to authenticate with an OIDC server, e.g. Google or an internal server like Authelia or Keycloak.
|
||||
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Option | Expected Entry | Example |
|
||||
+====================+===================================================================================================+=========================================+
|
||||
| Provider URL | The URL to connect to the OIDC server | https://accounts.google.com |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Client ID | Client ID provided by the OIDC server | [random String] |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Client Secret | Client secret provided by the OIDC server | [random String] |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Recheck identity | How often to recheck identity. | 12 hours |
|
||||
| | | |
|
||||
| | If the OIDC server is configured to remember the consent, the user should not receive any further | |
|
||||
| | | |
|
||||
| | login prompts and it can be ensured, that the user still exist on the server. | |
|
||||
| | | |
|
||||
| | Otherwise the user has actively grant access every time the identity is rechecked. In that case | |
|
||||
| | | |
|
||||
| | a higher interval would make sense. | |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Restrict to users | Only allow authorised users to access Gokapi that are listed below | true |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Scope for users | The OIDC scope that contains the user info | email |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Authorised users | List of users that are authorised to log in as an admin, separated by semicolon. | \*\@company.com;admin\@othercompany.com |
|
||||
| | | |
|
||||
| | ``*`` can be used as a wildcard | |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Restrict to groups | Only allow users that are part of authorised groups to access Gokapi | true |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Scope for groups | The OIDC scope that contains the group info | groups |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
| Authorised groups | List of groups that are authorised to log their users in as an admin, separated by semicolon. | admin;dev;gokapi-\* |
|
||||
| | | |
|
||||
| | ``*`` can be used as a wildcard | |
|
||||
+--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
|
||||
|
||||
.. note::
|
||||
If login is restricted to users and groups, both need to be present for a user to access. That means if a user has only one of the two factors, access to the admin menu will be denied.
|
||||
|
||||
.. note::
|
||||
A user will be authenticated until the time specified in ``Recheck identity`` has passed. To log out all users immediately, re-run the setup with `--reconfigure`` and complete it. Thereafter all active session will be deleted.
|
||||
|
||||
|
||||
.. note::
|
||||
If the OIDC provider is set up to remember consent, it might not be possible to log out through the Gokapi interface
|
||||
|
||||
|
||||
|
||||
|
||||
OIDC client/server configuration
|
||||
=======================================
|
||||
|
||||
When creating an OIDC client on the server, you will need to provide a **redirection URL**. Enter ``http[s]://[gokapi URL]/oauth-callback``
|
||||
|
||||
Tutorial for configuring OIDC servers and the correct client settings for Gokapi can be found in the :ref:`examples` page for the following servers:
|
||||
|
||||
* :ref:`oidcconfig_authelia`
|
||||
* :ref:`oidcconfig_keycloak`
|
||||
* :ref:`oidcconfig_google`
|
||||
|
||||
You can find a guide on how to create an OIDC client with Github at `Setting up GitHub OAuth 2.0 <https://docs.readme.com/docs/setting-up-github-oauth>`_ and a guide for Google at `Setting up OAuth 2.0 <https://support.google.com/cloud/answer/6158849>`_.
|
||||
|
||||
|
||||
Header Authentication
|
||||
************************
|
||||
|
||||
Only use this if you are running Gokapi behind a reverse proxy that is capable of authenticating users, e.g. by using Authelia or Authentik.
|
||||
Only use this if you are running Gokapi behind a reverse proxy that is capable of authenticating users, e.g. by using Authelia or Authentik. Keycloak does apparently not support this feauture.
|
||||
|
||||
Enter the key of the header that returns the username. For Authelia this would be ``Remote-User`` and for Authentik ``X-authentik-username``.
|
||||
Separate users with a semicolon or leave blank to allow any authenticated user, e.g. ``gokapiuser@gmail.com;companyadmin@gmail.com``
|
||||
@@ -158,7 +206,8 @@ This option disables Gokapis internal authentication completely, except for API
|
||||
- ``/uploadComplete``
|
||||
- ``/uploadStatus``
|
||||
|
||||
**Warning:** This option has potential to be *very* dangerous, only proceed if you know what you are doing!
|
||||
.. warning::
|
||||
This option has potential to be *very* dangerous, only proceed if you know what you are doing!
|
||||
|
||||
|
||||
|
||||
@@ -178,7 +227,10 @@ Stores files locally in the subdirectory ``data`` by default.
|
||||
Cloudstorage
|
||||
*********************
|
||||
|
||||
Stores files remotely on an S3 compatible server, e.g. Amazon AWS S3 or Backblaze B2. Please note that files will be stored in plain-text, if no encryption is selected later on.
|
||||
.. note::
|
||||
Files will be stored in plain-text, if no encryption is selected later on in the setup
|
||||
|
||||
Stores files remotely on an S3 compatible server, e.g. Amazon AWS S3 or Backblaze B2.
|
||||
|
||||
|
||||
It is highly recommended to create a new bucket for Gokapi and set it to "private", so that no file can be downloaded externally. For each download request Gokapi will create a public URL that is only valid for a couple of seconds, so that the file can be downloaded from the external server directly instead of routing it through the local server.
|
||||
@@ -205,7 +257,8 @@ The following data needs to be provided:
|
||||
Encryption
|
||||
""""""""""""""
|
||||
|
||||
*Warning: Encryption has not been audited.*
|
||||
.. warning::
|
||||
Encryption has not been audited.
|
||||
|
||||
There are three different encryption levels, level 1 encrypts only local files and level 2 encrypts local and files stored on cloud storage (e.g. AWS S3). Decryption of files on remote storage is done client-side, for which a 2MB library needs to be downloaded on first visit. End-to-End encryption (level 3) encrypts the files client-side, therefore even if the Gokapi server has been compromised, no data should leak to the attacker. If the decryption is done client-side, the dowload on mobile devices may be significantly slower.
|
||||
|
||||
@@ -228,7 +281,8 @@ You can choose to store the key in the configuration file, which is preferred if
|
||||
|
||||
If you are concerned that the configuration file can be read, you can also choose to enter a master password on startup. This needs to be entered in the command line however and Gokapi will not be able to start without it.
|
||||
|
||||
Please note: If you re-run the setup and enable encryption, unencrypted files will stay unencrypted. If you change any configuration related to encryption, all already encrypted files will be deleted.
|
||||
.. note::
|
||||
If you re-run the setup and enable encryption, unencrypted files will stay unencrypted. If you change any configuration related to encryption, all already encrypted files will be deleted.
|
||||
|
||||
************************
|
||||
Changing Configuration
|
||||
@@ -239,6 +293,9 @@ To change any settings set in the initial setup (e.g. your password or storage l
|
||||
If you are using Docker, shut down the running instance and create a new temporary container with the follwing command: ::
|
||||
|
||||
docker run --rm -p 127.0.0.1:53842:53842 -v gokapi-data:/app/data -v gokapi-config:/app/config f0rc3/gokapi:latest /app/gokapi --reconfigure
|
||||
|
||||
.. note::
|
||||
After completing the setup, all users will be logged out
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// CurrentConfigVersion is the version of the configuration structure. Used for upgrading
|
||||
const CurrentConfigVersion = 17
|
||||
const CurrentConfigVersion = 18
|
||||
|
||||
// DoUpgrade checks if an old version is present and updates it to the current version if required
|
||||
func DoUpgrade(settings *models.Configuration, env *environment.Environment) bool {
|
||||
@@ -59,10 +59,11 @@ func updateConfig(settings *models.Configuration, env *environment.Environment)
|
||||
}
|
||||
}
|
||||
// < v1.8.2
|
||||
if settings.ConfigVersion < 17 {
|
||||
if settings.ConfigVersion < 18 {
|
||||
if len(settings.Authentication.OAuthUsers) > 0 {
|
||||
settings.Authentication.OAuthUserScope = "email"
|
||||
}
|
||||
settings.Authentication.OAuthRecheckInterval = 168
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -476,6 +476,7 @@ type setupValues struct {
|
||||
OAuthScopeGroup setupEntry `form:"oauth_scope_groups"`
|
||||
OAuthRestrictUser setupEntry `form:"oauth_restrict_users" isBool:"true"`
|
||||
OAuthRestrictGroups setupEntry `form:"oauth_restrict_groups" isBool:"true"`
|
||||
OAuthRecheckInterval setupEntry `form:"oauth_recheck_interval" isInt:"true"`
|
||||
AuthHeaderKey setupEntry `form:"auth_headerkey"`
|
||||
AuthHeaderUsers setupEntry `form:"auth_header_users"`
|
||||
StorageSelection setupEntry `form:"storage_sel"`
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
</p>
|
||||
{{ else }}
|
||||
<p>
|
||||
You can now change the Gokapi configuration. For further information please refer to the <a href="https://gokapi.readthedocs.io/en/stable/" title="Gokapi Documentation" target="_blank">documentation</a>.
|
||||
You can now change the Gokapi configuration. For further information please refer to the <a href="https://gokapi.readthedocs.io/en/stable/" title="Gokapi Documentation" target="_blank" rel="noopener noreferrer">documentation</a>.
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
<div class="wizard-input-section">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
Please enter the OIDC client configuration. Multiple users and groups can be separated with a semicolon. The documentationTODO has examples on how to configure for different providers.<br>
|
||||
Please enter the OIDC client configuration. Multiple users and groups can be separated with a semicolon. The <a href="https://gokapi.readthedocs.io/en/stable/setup.html#oauth2-openid-connect" target="_blank" rel="noopener noreferrer">documentation</a> has examples on how to configure for different providers.<br>
|
||||
</p>
|
||||
|
||||
<div class="col-sm-8" style="width:90%">
|
||||
@@ -246,7 +246,18 @@
|
||||
<label for="oauth_secret">Client Secret:</label>
|
||||
<input type="text" class="form-control" id="oauth_secret" name="oauth_secret" placeholder="Client Secret" data-min="1" required data-validate="validateMinLength">
|
||||
</div>
|
||||
|
||||
<div class="col-sm-8" style="width:90%">
|
||||
Recheck identity every
|
||||
<select name="oauth_recheck_interval" id="oauth_recheck_interval" style="width:350px;" class="select form-control">
|
||||
<option value="6"> 6 hours</option>
|
||||
<option value="12" selected>12 hours</option>
|
||||
<option value="24">24 hours</option>
|
||||
<option value="72"> 3 days</option>
|
||||
<option value="168">7 days</option>
|
||||
<option value="336">14 days</option>
|
||||
<option value="720">30 days</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-8" style="width:90%">
|
||||
<br>Restrict to:
|
||||
<div class="oauthscopecontainer">
|
||||
@@ -266,16 +277,15 @@
|
||||
<input type="text" id="oauth_allowed_groups" name="oauth_allowed_groups" class="input-field" placeholder="Authorised groups" data-min="1" data-validate="validateMinLength" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-8" style="width:90%">
|
||||
<label for="oauth_redir"><br>Redirection URL:</label>
|
||||
<label for="oauth_redir"><br>Redirection URL:</label>
|
||||
{{ if .IsInitialSetup }}
|
||||
<input type="text" class="form-control" id="oauth_redir" name="oauth_redir" disabled value="http://127.0.0.1:53842/oauth-callback">
|
||||
{{ else }}
|
||||
<input type="text" class="form-control" id="oauth_redir" name="oauth_redir" disabled value="{{ .Settings.ServerUrl }}oauth-callback">
|
||||
{{ end }}
|
||||
</div>
|
||||
</div><br><br>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -698,6 +708,7 @@ function TestAWS(button) {
|
||||
document.getElementById("oauth_scope_groups").value = "{{ .Auth.OAuthGroupScope }}";
|
||||
document.getElementById("oauth_allowed_groups").disabled = false;
|
||||
document.getElementById("oauth_allowed_groups").value = "{{ .OAuthGroups }}";
|
||||
document.getElementById("oauth_recheck_interval").value = "{{ .Auth.OAuthRecheckInterval }}";
|
||||
{{ end }}
|
||||
break;
|
||||
case 2:
|
||||
@@ -721,6 +732,12 @@ function TestAWS(button) {
|
||||
|
||||
|
||||
wizard.on("submit", function(wizard) {
|
||||
|
||||
/* enable inputs again, otherwise they will not be submitted */
|
||||
document.getElementById("oauth_scope_groups").disabled = false;
|
||||
document.getElementById("oauth_scope_users").disabled = false;
|
||||
document.getElementById("oauth_allowed_users").disabled = false;
|
||||
document.getElementById("oauth_allowed_groups").disabled = false;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
|
||||
@@ -2,18 +2,19 @@ package models
|
||||
|
||||
// AuthenticationConfig holds configuration on how to authenticate to Gokapi admin menu
|
||||
type AuthenticationConfig struct {
|
||||
Method int `json:"Method"`
|
||||
SaltAdmin string `json:"SaltAdmin"`
|
||||
SaltFiles string `json:"SaltFiles"`
|
||||
Username string `json:"Username"`
|
||||
Password string `json:"Password"`
|
||||
HeaderKey string `json:"HeaderKey"`
|
||||
OAuthProvider string `json:"OauthProvider"`
|
||||
OAuthClientId string `json:"OAuthClientId"`
|
||||
OAuthClientSecret string `json:"OAuthClientSecret"`
|
||||
OAuthUserScope string `json:"OauthUserScope"`
|
||||
OAuthGroupScope string `json:"OauthGroupScope"`
|
||||
HeaderUsers []string `json:"HeaderUsers"`
|
||||
OAuthGroups []string `json:"OAuthGroups"`
|
||||
OAuthUsers []string `json:"OauthUsers"`
|
||||
Method int `json:"Method"`
|
||||
SaltAdmin string `json:"SaltAdmin"`
|
||||
SaltFiles string `json:"SaltFiles"`
|
||||
Username string `json:"Username"`
|
||||
Password string `json:"Password"`
|
||||
HeaderKey string `json:"HeaderKey"`
|
||||
OAuthProvider string `json:"OauthProvider"`
|
||||
OAuthClientId string `json:"OAuthClientId"`
|
||||
OAuthClientSecret string `json:"OAuthClientSecret"`
|
||||
OAuthUserScope string `json:"OauthUserScope"`
|
||||
OAuthGroupScope string `json:"OauthGroupScope"`
|
||||
OAuthRecheckInterval int `json:"OAuthRecheckInterval"`
|
||||
HeaderUsers []string `json:"HeaderUsers"`
|
||||
OAuthGroups []string `json:"OAuthGroups"`
|
||||
OAuthUsers []string `json:"OauthUsers"`
|
||||
}
|
||||
|
||||
@@ -301,7 +301,12 @@ func showLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if configuration.Get().Authentication.Method == authentication.OAuth2 {
|
||||
redirect(w, "oauth-login")
|
||||
// If user clicked logout, force consent
|
||||
if r.URL.Query().Has("consent") {
|
||||
redirect(w, "oauth-login?consent=true")
|
||||
} else {
|
||||
redirect(w, "oauth-login")
|
||||
}
|
||||
return
|
||||
}
|
||||
err := r.ParseForm()
|
||||
@@ -311,7 +316,9 @@ func showLogin(w http.ResponseWriter, r *http.Request) {
|
||||
failedLogin := false
|
||||
if pw != "" && user != "" {
|
||||
if authentication.IsCorrectUsernameAndPassword(user, pw) {
|
||||
sessionmanager.CreateSession(w)
|
||||
isOauth := configuration.Get().Authentication.Method == authentication.OAuth2
|
||||
interval := configuration.Get().Authentication.OAuthRecheckInterval
|
||||
sessionmanager.CreateSession(w, isOauth, interval)
|
||||
redirect(w, "admin")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/forceu/gokapi/internal/helper"
|
||||
"github.com/forceu/gokapi/internal/models"
|
||||
"github.com/forceu/gokapi/internal/storage"
|
||||
"github.com/forceu/gokapi/internal/webserver/authentication"
|
||||
"github.com/forceu/gokapi/internal/webserver/authentication/sessionmanager"
|
||||
"github.com/forceu/gokapi/internal/webserver/fileupload"
|
||||
"net/http"
|
||||
@@ -285,7 +286,9 @@ func isAuthorisedForApi(w http.ResponseWriter, request apiRequest) bool {
|
||||
sendError(w, http.StatusBadRequest, "Invalid request")
|
||||
return false
|
||||
}
|
||||
if IsValidApiKey(request.apiKey, true, perm) || sessionmanager.IsValidSession(w, request.request) {
|
||||
isOauth := configuration.Get().Authentication.Method == authentication.OAuth2
|
||||
interval := configuration.Get().Authentication.OAuthRecheckInterval
|
||||
if IsValidApiKey(request.apiKey, true, perm) || sessionmanager.IsValidSession(w, request.request, isOauth, interval) {
|
||||
return true
|
||||
}
|
||||
sendError(w, http.StatusUnauthorized, "Unauthorized")
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/forceu/gokapi/internal/configuration"
|
||||
"github.com/forceu/gokapi/internal/helper"
|
||||
"github.com/forceu/gokapi/internal/models"
|
||||
"github.com/forceu/gokapi/internal/webserver/authentication/sessionmanager"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -21,7 +23,7 @@ const Internal = 0
|
||||
// OAuth2 authentication retrieves the users email with Open Connect ID
|
||||
const OAuth2 = 1
|
||||
|
||||
// Header authentication relies on a header from a reverse proxy to parse the user name
|
||||
// Header authentication relies on a header from a reverse proxy to parse the username
|
||||
const Header = 2
|
||||
|
||||
// Disabled authentication ignores all internal authentication procedures. A reverse proxy needs to restrict access
|
||||
@@ -67,17 +69,40 @@ func isGrantedHeader(r *http.Request) bool {
|
||||
|
||||
func isUserInArray(userEntered string, allowedUsers []string) bool {
|
||||
for _, allowedUser := range allowedUsers {
|
||||
if strings.ToLower(allowedUser) == strings.ToLower(userEntered) {
|
||||
matches, err := matchesWithWildcard(strings.ToLower(allowedUser), strings.ToLower(userEntered))
|
||||
helper.Check(err)
|
||||
if matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchesWithWildcard(pattern, input string) (bool, error) {
|
||||
components := strings.Split(pattern, "*")
|
||||
if len(components) == 1 {
|
||||
// if len is 1, there are no *'s, return exact match pattern
|
||||
return regexp.MatchString("^"+pattern+"$", input)
|
||||
}
|
||||
var result strings.Builder
|
||||
for i, literal := range components {
|
||||
// Replace * with .*
|
||||
if i > 0 {
|
||||
result.WriteString(".*")
|
||||
}
|
||||
// Quote any regular expression meta characters in the
|
||||
// literal text.
|
||||
result.WriteString(regexp.QuoteMeta(literal))
|
||||
}
|
||||
return regexp.MatchString("^"+result.String()+"$", input)
|
||||
}
|
||||
|
||||
func isGroupInArray(userGroups []string, allowedGroups []string) bool {
|
||||
for _, group := range userGroups {
|
||||
for _, allowedGroup := range allowedGroups {
|
||||
if strings.ToLower(allowedGroup) == strings.ToLower(group) {
|
||||
matches, err := matchesWithWildcard(strings.ToLower(allowedGroup), strings.ToLower(group))
|
||||
helper.Check(err)
|
||||
if matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -85,7 +110,7 @@ func isGroupInArray(userGroups []string, allowedGroups []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func extractOauthGroups(userInfo OAuthUserInfo, groupScope string) ([]string, error) {
|
||||
func extractOauthGroups(userInfo OAuthUserClaims, groupScope string) ([]string, error) {
|
||||
var claims json.RawMessage
|
||||
var data map[string]interface{}
|
||||
|
||||
@@ -113,7 +138,7 @@ func extractOauthGroups(userInfo OAuthUserInfo, groupScope string) ([]string, er
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func extractFieldValue(userInfo OAuthUserInfo, fieldName string) (string, error) {
|
||||
func extractFieldValue(userInfo OAuthUserClaims, fieldName string) (string, error) {
|
||||
var claims json.RawMessage
|
||||
|
||||
err := userInfo.Claims(&claims)
|
||||
@@ -141,31 +166,41 @@ func extractFieldValue(userInfo OAuthUserInfo, fieldName string) (string, error)
|
||||
}
|
||||
|
||||
// OAuthUserInfo is used to make testing easier. This results in an additional parameter for the subject unfortunately
|
||||
type OAuthUserInfo interface {
|
||||
type OAuthUserInfo struct {
|
||||
Subject string
|
||||
Email string
|
||||
ClaimsSent OAuthUserClaims
|
||||
}
|
||||
|
||||
// OAuthUserClaims contains the claims
|
||||
type OAuthUserClaims interface {
|
||||
Claims(v interface{}) error
|
||||
}
|
||||
|
||||
// CheckOauthUserAndRedirect checks if the user is allowed to use the Gokapi instance
|
||||
func CheckOauthUserAndRedirect(userInfo OAuthUserInfo, userInfoSubject string, w http.ResponseWriter) error {
|
||||
func CheckOauthUserAndRedirect(userInfo OAuthUserInfo, w http.ResponseWriter) error {
|
||||
var username string
|
||||
var groups []string
|
||||
var err error
|
||||
|
||||
if authSettings.OAuthUserScope != "" {
|
||||
username, err = extractFieldValue(userInfo, authSettings.OAuthUserScope)
|
||||
if err != nil {
|
||||
return err
|
||||
if authSettings.OAuthUserScope == "email" {
|
||||
username = userInfo.Email
|
||||
} else {
|
||||
username, err = extractFieldValue(userInfo.ClaimsSent, authSettings.OAuthUserScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if authSettings.OAuthGroupScope != "" {
|
||||
groups, err = extractOauthGroups(userInfo, authSettings.OAuthGroupScope)
|
||||
groups, err = extractOauthGroups(userInfo.ClaimsSent, authSettings.OAuthGroupScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if isValidOauthUser(userInfoSubject, username, groups) {
|
||||
// TODO revoke session if oauth is not valid any more
|
||||
sessionmanager.CreateSession(w)
|
||||
if isValidOauthUser(userInfo, username, groups) {
|
||||
sessionmanager.CreateSession(w, authSettings.Method == OAuth2, authSettings.OAuthRecheckInterval)
|
||||
redirect(w, "admin")
|
||||
return nil
|
||||
}
|
||||
@@ -173,8 +208,8 @@ func CheckOauthUserAndRedirect(userInfo OAuthUserInfo, userInfoSubject string, w
|
||||
return nil
|
||||
}
|
||||
|
||||
func isValidOauthUser(userInfoSubject string, username string, groups []string) bool {
|
||||
if userInfoSubject == "" {
|
||||
func isValidOauthUser(userInfo OAuthUserInfo, username string, groups []string) bool {
|
||||
if userInfo.Subject == "" {
|
||||
return false
|
||||
}
|
||||
isValidUser := true
|
||||
@@ -190,7 +225,7 @@ func isValidOauthUser(userInfoSubject string, username string, groups []string)
|
||||
|
||||
// isGrantedSession returns true if the user holds a valid internal session cookie
|
||||
func isGrantedSession(w http.ResponseWriter, r *http.Request) bool {
|
||||
return sessionmanager.IsValidSession(w, r)
|
||||
return sessionmanager.IsValidSession(w, r, authSettings.Method == OAuth2, authSettings.OAuthRecheckInterval)
|
||||
}
|
||||
|
||||
// IsCorrectUsernameAndPassword checks if a provided username and password is correct
|
||||
@@ -216,7 +251,11 @@ func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
if authSettings.Method == Internal || authSettings.Method == OAuth2 {
|
||||
sessionmanager.LogoutSession(w, r)
|
||||
}
|
||||
redirect(w, "login")
|
||||
if authSettings.Method == OAuth2 {
|
||||
redirect(w, "login?consent=true")
|
||||
} else {
|
||||
redirect(w, "login")
|
||||
}
|
||||
}
|
||||
|
||||
// IsLogoutAvailable returns true if a logout button should be shown with the current form of authentication
|
||||
|
||||
@@ -3,7 +3,7 @@ package authentication
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"fmt"
|
||||
"github.com/forceu/gokapi/internal/configuration"
|
||||
"github.com/forceu/gokapi/internal/models"
|
||||
"github.com/forceu/gokapi/internal/test"
|
||||
@@ -107,23 +107,90 @@ func TestRedirect(t *testing.T) {
|
||||
|
||||
func TestIsValidOauthUser(t *testing.T) {
|
||||
Init(modelOauth)
|
||||
info := oidc.UserInfo{Subject: "randomid"}
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "", []string{}), true)
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{"test2"}), true)
|
||||
info := OAuthUserInfo{Subject: "randomid"}
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "", []string{}), true)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{"test2"}), true)
|
||||
authSettings.OAuthUserScope = "user"
|
||||
authSettings.OAuthUsers = []string{"otheruser"}
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{}), true)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{}), true)
|
||||
authSettings.OAuthGroupScope = "group"
|
||||
authSettings.OAuthGroups = []string{"othergroup"}
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{"testgroup"}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{"testgroup", "othergroup"}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{"othergroup"}), true)
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{"testgroup", "othergroup"}), true)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{"testgroup"}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{"testgroup", "othergroup"}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{"othergroup"}), true)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{"testgroup", "othergroup"}), true)
|
||||
info.Subject = ""
|
||||
test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{"testgroup", "othergroup"}), false)
|
||||
test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{"testgroup", "othergroup"}), false)
|
||||
}
|
||||
|
||||
func TestWildcardMatch(t *testing.T) {
|
||||
type testPattern struct {
|
||||
Pattern string
|
||||
Input string
|
||||
Result bool
|
||||
}
|
||||
tests := []testPattern{{
|
||||
Pattern: "test",
|
||||
Input: "test",
|
||||
Result: true,
|
||||
}, {
|
||||
Pattern: "test*",
|
||||
Input: "test",
|
||||
Result: true,
|
||||
}, {
|
||||
Pattern: "*test",
|
||||
Input: "test",
|
||||
Result: true,
|
||||
}, {
|
||||
Pattern: "te*st",
|
||||
Input: "test",
|
||||
Result: true,
|
||||
}, {
|
||||
Pattern: "test*",
|
||||
Input: "1test",
|
||||
Result: false,
|
||||
}, {
|
||||
Pattern: "*test",
|
||||
Input: "test1",
|
||||
Result: false,
|
||||
}, {
|
||||
Pattern: "te*st",
|
||||
Input: "teeeeeeeest",
|
||||
Result: true,
|
||||
}, {
|
||||
Pattern: "te*st",
|
||||
Input: "teast",
|
||||
Result: true,
|
||||
}, {
|
||||
Pattern: "te*st",
|
||||
Input: "te@st",
|
||||
Result: true,
|
||||
}, {
|
||||
Pattern: "*@github.com",
|
||||
Input: "email@github.com",
|
||||
Result: true,
|
||||
}, {
|
||||
Pattern: "@github.com",
|
||||
Input: "email@github.com",
|
||||
Result: false,
|
||||
}, {
|
||||
Pattern: "@github.com",
|
||||
Input: "email@gokapi.com",
|
||||
Result: false,
|
||||
}, {
|
||||
Pattern: "*@github.com",
|
||||
Input: "email@gokapi.com",
|
||||
Result: false,
|
||||
}}
|
||||
for _, patternTest := range tests {
|
||||
fmt.Printf("Testing: %s == %s, expecting %v\n", patternTest.Pattern, patternTest.Input, patternTest.Result)
|
||||
result, err := matchesWithWildcard(patternTest.Pattern, patternTest.Input)
|
||||
test.IsNil(t, err)
|
||||
test.IsEqualBool(t, result, patternTest.Result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogout(t *testing.T) {
|
||||
@@ -133,11 +200,10 @@ func TestLogout(t *testing.T) {
|
||||
}
|
||||
|
||||
type testInfo struct {
|
||||
Output []byte
|
||||
Subject string
|
||||
Output []byte
|
||||
}
|
||||
|
||||
func (t *testInfo) Claims(v interface{}) error {
|
||||
func (t testInfo) Claims(v interface{}) error {
|
||||
if t.Output == nil {
|
||||
return errors.New("oidc: claims not set")
|
||||
}
|
||||
@@ -146,32 +212,42 @@ func (t *testInfo) Claims(v interface{}) error {
|
||||
|
||||
func TestCheckOauthUser(t *testing.T) {
|
||||
Init(modelOauth)
|
||||
info := testInfo{Output: []byte(`{"amr":["pwd","hwk","user","pin","mfa"],"aud":["gokapi-dev"],"auth_time":1705573822,"azp":"gokapi-dev","client_id":"gokapi-dev","email":"test@test.com","email_verified":true,"groups":["admins","dev"],"iat":1705577400,"iss":"https://auth.test.com","name":"gokapi","preferred_username":"gokapi","rat":1705577400,"sub":"944444cf3e-0546-44f2-acfa-a94444444360"}`)}
|
||||
output, err := getOuthUserOutput(t, &info, info.Subject)
|
||||
info := OAuthUserInfo{
|
||||
ClaimsSent: testInfo{Output: []byte(`{"amr":["pwd","hwk","user","pin","mfa"],"aud":["gokapi-dev"],"auth_time":1705573822,"azp":"gokapi-dev","client_id":"gokapi-dev","email":"test@test.com","email_verified":true,"groups":["admins","dev"],"iat":1705577400,"iss":"https://auth.test.com","name":"gokapi","preferred_username":"gokapi","rat":1705577400,"sub":"944444cf3e-0546-44f2-acfa-a94444444360"}`)},
|
||||
}
|
||||
output, err := getOuthUserOutput(t, info)
|
||||
test.IsNil(t, err)
|
||||
test.IsEqualString(t, redirectsToSite(output), "error-auth")
|
||||
|
||||
info.Subject = "random"
|
||||
output, err = getOuthUserOutput(t, &info, info.Subject)
|
||||
output, err = getOuthUserOutput(t, info)
|
||||
test.IsNil(t, err)
|
||||
test.IsEqualString(t, redirectsToSite(output), "admin")
|
||||
|
||||
info.Email = "test@test.com"
|
||||
authSettings.OAuthUserScope = "email"
|
||||
authSettings.OAuthUsers = []string{"otheruser"}
|
||||
output, err = getOuthUserOutput(t, &info, info.Subject)
|
||||
output, err = getOuthUserOutput(t, info)
|
||||
test.IsNil(t, err)
|
||||
test.IsEqualString(t, redirectsToSite(output), "error-auth")
|
||||
|
||||
authSettings.OAuthUsers = []string{"test@test.com"}
|
||||
output, err = getOuthUserOutput(t, &info, info.Subject)
|
||||
output, err = getOuthUserOutput(t, info)
|
||||
test.IsNil(t, err)
|
||||
test.IsEqualString(t, redirectsToSite(output), "admin")
|
||||
|
||||
authSettings.OAuthUsers = []string{"otheruser@test"}
|
||||
output, err = getOuthUserOutput(t, &info, info.Subject)
|
||||
output, err = getOuthUserOutput(t, info)
|
||||
test.IsNil(t, err)
|
||||
test.IsEqualString(t, redirectsToSite(output), "error-auth")
|
||||
|
||||
authSettings.OAuthUserScope = "invalidScope"
|
||||
output, err = getOuthUserOutput(t, &info, info.Subject)
|
||||
output, err = getOuthUserOutput(t, info)
|
||||
test.IsNotNil(t, err)
|
||||
info.Output = []byte("{invalid")
|
||||
output, err = getOuthUserOutput(t, &info, info.Subject)
|
||||
|
||||
newClaims := testInfo{Output: []byte("{invalid")}
|
||||
info.ClaimsSent = newClaims
|
||||
output, err = getOuthUserOutput(t, info)
|
||||
test.IsNotNil(t, err)
|
||||
}
|
||||
|
||||
@@ -185,10 +261,10 @@ func redirectsToSite(input string) string {
|
||||
return "other"
|
||||
}
|
||||
|
||||
func getOuthUserOutput(t *testing.T, info OAuthUserInfo, infoSubject string) (string, error) {
|
||||
func getOuthUserOutput(t *testing.T, info OAuthUserInfo) (string, error) {
|
||||
t.Helper()
|
||||
w := httptest.NewRecorder()
|
||||
err := CheckOauthUserAndRedirect(info, infoSubject, w)
|
||||
err := CheckOauthUserAndRedirect(info, w)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ func Init(baseUrl string, credentials models.AuthenticationConfig) {
|
||||
}
|
||||
|
||||
// HandlerLogin is a handler for showing the login screen
|
||||
func HandlerLogin(w http.ResponseWriter, r *http.Request) {
|
||||
initLogin(w, r, false)
|
||||
func HandlerLogin(w http.ResponseWriter, r *http.Request) { // If user clicked logout, force consent
|
||||
initLogin(w, r, r.URL.Query().Has("consent"))
|
||||
}
|
||||
|
||||
func initLogin(w http.ResponseWriter, r *http.Request, showConsentScreen bool) {
|
||||
@@ -97,7 +97,12 @@ func HandlerCallback(w http.ResponseWriter, r *http.Request) {
|
||||
showOauthErrorPage(w, r, "Failed to get userinfo: "+err.Error())
|
||||
return
|
||||
}
|
||||
err = authentication.CheckOauthUserAndRedirect(userInfo, userInfo.Subject, w)
|
||||
info := authentication.OAuthUserInfo{
|
||||
Subject: userInfo.Subject,
|
||||
Email: userInfo.Email,
|
||||
ClaimsSent: userInfo,
|
||||
}
|
||||
err = authentication.CheckOauthUserAndRedirect(info, w)
|
||||
if err != nil {
|
||||
showOauthErrorPage(w, r, "Failed to extract scope value: "+err.Error())
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ const cookieLifeAdmin = 30 * 24 * time.Hour
|
||||
// IsValidSession checks if the user is submitting a valid session token
|
||||
// If valid session is found, useSession will be called
|
||||
// Returns true if authenticated, otherwise false
|
||||
func IsValidSession(w http.ResponseWriter, r *http.Request) bool {
|
||||
func IsValidSession(w http.ResponseWriter, r *http.Request, isOauth bool, OAuthRecheckInterval int) bool {
|
||||
cookie, err := r.Cookie("session_token")
|
||||
if err == nil {
|
||||
sessionString := cookie.Value
|
||||
if sessionString != "" {
|
||||
session, ok := database.GetSession(sessionString)
|
||||
if ok {
|
||||
return useSession(w, sessionString, session)
|
||||
return useSession(w, sessionString, session, isOauth, OAuthRecheckInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,13 +38,13 @@ func IsValidSession(w http.ResponseWriter, r *http.Request) bool {
|
||||
// if it has // been used for more than an hour to limit session hijacking
|
||||
// Returns true if session is still valid
|
||||
// Returns false if session is invalid (and deletes it)
|
||||
func useSession(w http.ResponseWriter, id string, session models.Session) bool {
|
||||
func useSession(w http.ResponseWriter, id string, session models.Session, isOauth bool, OAuthRecheckInterval int) bool {
|
||||
if session.ValidUntil < time.Now().Unix() {
|
||||
database.DeleteSession(id)
|
||||
return false
|
||||
}
|
||||
if session.RenewAt < time.Now().Unix() {
|
||||
CreateSession(w)
|
||||
CreateSession(w, isOauth, OAuthRecheckInterval)
|
||||
database.DeleteSession(id)
|
||||
}
|
||||
return true
|
||||
@@ -52,13 +52,18 @@ func useSession(w http.ResponseWriter, id string, session models.Session) bool {
|
||||
|
||||
// CreateSession creates a new session - called after login with correct username / password
|
||||
// If sessions parameter is nil, it will be loaded from config
|
||||
func CreateSession(w http.ResponseWriter) {
|
||||
func CreateSession(w http.ResponseWriter, isOauth bool, OAuthRecheckInterval int) {
|
||||
timeExpiry := time.Now().Add(cookieLifeAdmin)
|
||||
if isOauth {
|
||||
timeExpiry = time.Now().Add(time.Duration(OAuthRecheckInterval) * time.Hour)
|
||||
}
|
||||
|
||||
sessionString := helper.GenerateRandomString(60)
|
||||
database.SaveSession(sessionString, models.Session{
|
||||
RenewAt: time.Now().Add(12 * time.Hour).Unix(),
|
||||
ValidUntil: time.Now().Add(cookieLifeAdmin).Unix(),
|
||||
ValidUntil: timeExpiry.Unix(),
|
||||
})
|
||||
writeSessionCookie(w, sessionString, time.Now().Add(cookieLifeAdmin))
|
||||
writeSessionCookie(w, sessionString, timeExpiry)
|
||||
}
|
||||
|
||||
// LogoutSession logs out user and deletes session
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<h2 class="card-title">Unauthorised user</h2>
|
||||
<br>
|
||||
<p class="card-text">Login with OAuth provider was sucessful, however this user is not authorised by Gokapi.</p><br><br>
|
||||
<a href="./login" class="card-link">Log in as different user</a>
|
||||
<a href="./login?consent=true" class="card-link">Log in as different user</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{{ end}}
|
||||
<p class="text-monospace">{{ .ErrorGenericMessage }}</blockquote></p><br>
|
||||
{{ end }}
|
||||
<a href="./login" class="card-link">Try again</a>
|
||||
<a href="./login?consent=true" class="card-link">Try again</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
</main>
|
||||
|
||||
<footer class="mt-auto text-white-50">
|
||||
<p> Powered by <a href="https://github.com/Forceu/Gokapi" target="_blank">Gokapi v{{template "version"}}</a></p>
|
||||
<p> Powered by <a href="https://github.com/Forceu/Gokapi" target="_blank" rel="noopener noreferrer">Gokapi v{{template "version"}}</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user