Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial functionality #1

Merged
merged 34 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4abf1bd
Add basic provider functionality
cbartz Jul 20, 2023
c4b65eb
Add basic group functionality
cbartz Jul 21, 2023
61696be
Restructure code
cbartz Jul 21, 2023
3a6760b
Use iterator instead of iterable in get_members
cbartz Jul 21, 2023
25f031c
WIP: Persist group information in database
cbartz Jul 24, 2023
7abd7c6
WIP: Use indico.core.db.db
cbartz Jul 25, 2023
536f095
Restructure code
cbartz Jul 25, 2023
7e2451e
Disable indico pytest plugins
cbartz Jul 25, 2023
4325a07
Create Indico plugin
cbartz Jul 25, 2023
4bca315
Add integration tests
cbartz Jul 26, 2023
52a2aff
Add/fix tests
cbartz Jul 26, 2023
c609f42
Adapt code so that tox run passes
cbartz Jul 26, 2023
bede13a
Remove memory group provider
cbartz Jul 27, 2023
89fc2ce
Use random values in tests
cbartz Jul 27, 2023
82a5e04
Add setup configuration
cbartz Jul 27, 2023
9a419cb
Set ignore_missing_imports to true for mypy
cbartz Jul 27, 2023
aff975d
Add README
cbartz Jul 27, 2023
1a38fed
Add Github Actions
cbartz Jul 27, 2023
6211a80
Add note to README that SAML auth provider must be used
cbartz Jul 27, 2023
50f8591
Shorten requirements.txt
cbartz Jul 27, 2023
5a52eaf
ci: Install requirements for xmlsec
cbartz Jul 27, 2023
94898fd
Init app after provider registration in tests
cbartz Jul 28, 2023
35d368d
Add experimental status warning to README
cbartz Jul 28, 2023
ac5a4ad
Add license header to migration module
cbartz Jul 28, 2023
f201d22
Add pre-run-script to install xmlsec deps
cbartz Jul 28, 2023
a477a87
Add libpq-dev to install-packages.sh
cbartz Jul 28, 2023
712f905
Adapt README
cbartz Jul 28, 2023
2e43854
Change author to is-devops in setup.cfg
cbartz Jul 28, 2023
19b29b7
Add blank line to EOF
cbartz Jul 28, 2023
eca8641
Change author to myself
cbartz Jul 28, 2023
c234ad4
Remove sudo from install-libs.sh
cbartz Jul 31, 2023
4355d9b
Explain why pylint:no-member has been disabled
cbartz Jul 31, 2023
e92b1f0
Revert "Change author to myself"
cbartz Jul 31, 2023
beb67d9
Add docstrings to methods in sql module
cbartz Jul 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/test-pre-script.sh
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
18 changes: 18 additions & 0 deletions .github/workflows/integration_test.yaml
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
11 changes: 11 additions & 0 deletions .github/workflows/test.yaml
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
138 changes: 138 additions & 0 deletions README.md
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",
}
}
```
4 changes: 4 additions & 0 deletions flask_multipass_saml_groups/__init__.py
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."""
4 changes: 4 additions & 0 deletions flask_multipass_saml_groups/group_provider/__init__.py
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."""
85 changes: 85 additions & 0 deletions flask_multipass_saml_groups/group_provider/base.py
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.
"""
Loading