-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add basic provider functionality * Add basic group functionality * Restructure code * Use iterator instead of iterable in get_members * WIP: Persist group information in database * WIP: Use indico.core.db.db * Restructure code * Disable indico pytest plugins * Create Indico plugin * Add integration tests * Add/fix tests * Adapt code so that tox run passes * Remove memory group provider * Use random values in tests * Add setup configuration * Set ignore_missing_imports to true for mypy * Add README * Add Github Actions * Add note to README that SAML auth provider must be used * Shorten requirements.txt * ci: Install requirements for xmlsec * Init app after provider registration in tests * Add experimental status warning to README * Add license header to migration module * Add pre-run-script to install xmlsec deps * Add libpq-dev to install-packages.sh * Adapt README * Change author to is-devops in setup.cfg * Add blank line to EOF * Change author to myself * Remove sudo from install-libs.sh * Explain why pylint:no-member has been disabled * Revert "Change author to myself" This reverts commit eca8641. * Add docstrings to methods in sql module
- Loading branch information
Showing
34 changed files
with
2,166 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/usr/bin/env bash | ||
# Copyright 2023 Canonical Ltd. | ||
# See LICENSE file for licensing details. | ||
|
||
# Call script with sudo to install the required native libraries for installing the plugin's dependencies | ||
sudo bash -xe "$(dirname "$0")"/../install-libs.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: Integration tests | ||
|
||
on: | ||
pull_request: | ||
|
||
jobs: | ||
integration-tests: | ||
runs-on: ubuntu-latest | ||
name: Integration Tests | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Install required native libraries | ||
run: sudo bash -xe install-libs.sh | ||
- name: Install tox | ||
run: python3 -m pip install tox | ||
- name: Run integration tests | ||
run: | | ||
tox -e integration |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name: Tests | ||
|
||
on: | ||
pull_request: | ||
|
||
jobs: | ||
unit-tests: | ||
uses: canonical/operator-workflows/.github/workflows/test.yaml@main | ||
secrets: inherit | ||
with: | ||
pre-run-script: .github/test-pre-script.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
# Flask-Multipass-SAML-Groups | ||
|
||
This package provides an identity provider for [Flask-Multipass](https://github.com/indico/flask-multipass), | ||
which allows you to use SAML groups. It is designed to be used | ||
as a plugin for [Indico](https://github.com/indico/indico). | ||
|
||
> **Warning** | ||
> The current code base has not been extensively tested and should be considered experimental. | ||
|
||
## Motivation | ||
|
||
The current SAML identity provider in Flask-Multipass does not support groups (see [issue](https://github.com/indico/flask-multipass/issues/66)), | ||
but groups are a very useful feature for Indico. This plugin provides a solution to this problem. | ||
|
||
|
||
## Installation | ||
|
||
### Package installation | ||
You need to install the package on the same virtual environment as your Indico instance. | ||
You might use the following commands to switch to the Indico environment | ||
|
||
```bash | ||
su - indico | ||
source ~/.venv/bin/activate | ||
``` | ||
|
||
Some of the dependencies, like [xmlsec](https://xmlsec.readthedocs.io/en/stable/install.html), | ||
require native libraries to be installed on the system. To install these libraries on an | ||
Ubuntu system, you can use the `install-packages.sh` file: | ||
|
||
```bash | ||
sudo bash install-libs.sh | ||
``` | ||
|
||
You can then install this package either via local source: | ||
|
||
```bash | ||
git clone https://github.com/canonical/flask-multipass-saml-groups.git | ||
cd flask-multipass-saml-groups | ||
python setup.py install | ||
``` | ||
|
||
or with pip: | ||
|
||
```bash | ||
pip install git+https://github.com/canonical/flask-multipass-saml-groups.git | ||
``` | ||
|
||
|
||
### Indico setup | ||
|
||
In your Indico setup, you should see that the plugin is now available: | ||
|
||
```bash | ||
indico setup list-plugins | ||
``` | ||
|
||
In order to activate the plugin, you must add it to the list of active plugins in your Indico configuration file: | ||
|
||
```python | ||
PLUGINS = { ..., 'saml_groups' } | ||
``` | ||
|
||
Beyond that, the plugin uses its own database tables to persist the groups. Therefore you need to run | ||
|
||
```bash | ||
indico db --all-plugins upgrade | ||
``` | ||
See [here](https://docs.getindico.io/en/latest/installation/plugins/) for more information on installing | ||
Indico plugins. | ||
|
||
|
||
### Identity provider configuration | ||
The configuration is almost identical to the SAML identity provider in Flask-Multipass, | ||
but you should use the type `saml_groups` instead of `saml`. The identity provider must be used | ||
together with the SAML auth Provider, in order to receive the SAML groups in the authentication | ||
data. | ||
|
||
The following is an example section in `indico.conf`: | ||
```python | ||
|
||
_my_saml_config = { | ||
'sp': { | ||
'entityId': 'https://events.example.com', | ||
'x509cert': '', | ||
'privateKey': '', | ||
}, | ||
'idp': { | ||
'entityId': 'https://login.example.com', | ||
'x509cert': 'YmFzZTY0IGVuY29kZWQgY2VydAo', | ||
'singleSignOnService': { | ||
'url': 'https://login.example.com/saml/', | ||
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' | ||
}, | ||
'singleLogoutService': { | ||
'url': 'https://login.example.com/+logout', | ||
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' | ||
} | ||
}, | ||
'security': { | ||
'nameIdEncrypted': False, | ||
'authnRequestsSigned': False, | ||
'logoutRequestSigned': False, | ||
'logoutResponseSigned': False, | ||
'signMetadata': False, | ||
'wantMessagesSigned': False, | ||
'wantAssertionsSigned': False, | ||
'wantNameId' : False, | ||
'wantNameIdEncrypted': False, | ||
'wantAssertionsEncrypted': False, | ||
'allowSingleLabelDomains': False, | ||
'signatureAlgorithm': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', | ||
'digestAlgorithm': 'http://www.w3.org/2001/04/xmlenc#sha256' | ||
}, | ||
} | ||
|
||
MULTIPASS_AUTH_PROVIDERS = { | ||
'ubuntu': { | ||
'type': 'saml', | ||
'title': 'SAML SSO', | ||
'saml_config': _my_saml_config, | ||
}, | ||
} | ||
IDENTITY_PROVIDERS = { | ||
"ubuntu": { | ||
"type": "saml_groups", | ||
"trusted_email": True, | ||
"mapping": { | ||
"user_name": "username", | ||
"first_name": "fullname", | ||
"last_name": "", | ||
"email": "email", | ||
}, | ||
"identifier_field": "openid", | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Copyright 2023 Canonical Ltd. | ||
# See LICENSE file for licensing details. | ||
|
||
"""The package containing the SAML Groups plugin.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Copyright 2023 Canonical Ltd. | ||
# See LICENSE file for licensing details. | ||
|
||
"""The package containing the group providers for the SAML Groups plugin.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Copyright 2023 Canonical Ltd. | ||
# See LICENSE file for licensing details. | ||
"""Defines the interface for a group provider.""" | ||
|
||
from abc import ABCMeta, abstractmethod | ||
from typing import Iterable, Optional | ||
|
||
from flask_multipass import Group, IdentityProvider | ||
|
||
|
||
class GroupProvider(metaclass=ABCMeta): | ||
"""A group provider is responsible for managing groups and their members. | ||
Attrs: | ||
group_class (type): The class to use for groups. | ||
""" | ||
|
||
group_class = Group | ||
|
||
def __init__(self, identity_provider: IdentityProvider): | ||
"""Initialize the group provider. | ||
Args: | ||
identity_provider: The associated identity provider. Usually required because the group | ||
needs to know the identity provider. | ||
""" | ||
|
||
@abstractmethod | ||
def add_group(self, name: str) -> None: # pragma: no cover | ||
"""Add a group. | ||
Args: | ||
name: The name of the group. | ||
""" | ||
|
||
@abstractmethod | ||
def get_group(self, name: str) -> Optional[Group]: # pragma: no cover | ||
"""Get a group. | ||
Args: | ||
name: The name of the group. | ||
Returns: | ||
The group or None if it does not exist. | ||
""" | ||
return None | ||
|
||
@abstractmethod | ||
def get_groups(self) -> Iterable[Group]: # pragma: no cover | ||
"""Get all groups. | ||
Returns: | ||
An iterable of all groups. | ||
""" | ||
return [] | ||
|
||
@abstractmethod | ||
def get_user_groups(self, identifier: str) -> Iterable[Group]: # pragma: no cover | ||
"""Get all groups a user is a member of. | ||
Args: | ||
identifier: The unique user identifier used by the provider. | ||
Returns: | ||
iterable: An iterable of groups the user is a member of. | ||
""" | ||
return [] | ||
|
||
@abstractmethod | ||
def add_group_member(self, identifier: str, group_name: str) -> None: # pragma: no cover | ||
"""Add a user to a group. | ||
Args: | ||
identifier: The unique user identifier used by the provider. | ||
group_name: The name of the group. | ||
""" | ||
|
||
@abstractmethod | ||
def remove_group_member(self, identifier: str, group_name: str) -> None: # pragma: no cover | ||
"""Remove a user from a group. | ||
Args: | ||
identifier: The unique user identifier used by the provider. | ||
group_name: The name of the group. | ||
""" |
Oops, something went wrong.