Repository: learningequality/kolibri

Issue:
openid_connect plugin for BSF
### Summary
Create a plugin called `openid_connect`, unabled by default, that will allow kolibri users to authenticate against an [OpenID Connect](https://openid.net/connect/) provider.
The setup is ready to work with the [olip-api](https://gitlab.com/bibliosansfrontieres/olip/olip-api) developed by the BSF project


### Reviewer guidance
1 . Ensure you have the server provider working either locally ([Docker setup](#docker-setup)) in an development environment or using a remote installation (as the one at http://70.167.220.187:443/ )
2. set the variable environment API_URL with the url of the server provider
3. enable this plugin in kolibri
4. After login  into the platform with an username existing in the platform (and not existing in the local kolibri database), login into kolibri should be possible just accepting the confirmation request.


### References

- [OpenID Connect specs](https://openid.net/specs/openid-connect-basic-1_0.html#CodeFlow)
- [OIDC glossary ](https://infosec.mozilla.org/guidelines/iam/openid_connect.html)
- [OLIP repositories](https://gitlab.com/bibliosansfrontieres/olip)
- [OLIP project architecture](https://docs.google.com/document/d/1bWLrUtpUtAuHTd_7phSZnH4b08Dn1bHAaB_K2Ge8_hI/edit?usp=sharing)


### Contributor Checklist


PR process:

- [ ] PR has the correct target branch and milestone
- [ ] PR has 'needs review' or 'work-in-progress' label
- [ ] If PR is ready for review, a reviewer has been added. (Don't use 'Assignees')
- [ ] If this is an important user-facing change, PR or related issue has a 'changelog' label
- [ ] If this includes an internal dependency change, a link to the diff is provided

Testing:

- [ ] Contributor has fully tested the PR manually
- [ ] If there are any front-end changes, before/after screenshots are included
- [ ] Critical user journeys are covered by Gherkin stories
- [ ] Critical and brittle code paths are covered by unit tests

### Reviewer Checklist

- Automated test coverage is satisfactory
- PR is fully functional
- PR has been tested for [accessibility regressions](http://kolibri-dev.readthedocs.io/en/develop/manual_testing.html#accessibility-a11y-testing)
- External dependency files were updated if necessary (`yarn` and `pip`)
- Documentation is updated
- Contributor is in AUTHORS.md

# Docker Setup
Instructions to set BSF platform environment up running using Docker:
Previous note, there's a chicken-egg problem: The ip of the olip-api container is going to be needed to setup the olip-api container, in the `API_BASE_URL` env variable. In the example below it's 172.17.0.2. In other systems it can be different. The only way to know it is executing `docker inspect olip-api` after creating the container. If it's not the one written below, stop & remove the container and create it again with the assigned ip.
1. `docker run -d --name olip-api   --mount type=bind,source="$(pwd)"/,target=/data  -e HOST_DATA_DIR=/data -e API_BASE_URL=http://172.17.0.2:5002    -e SQLALCHEMY_DATABASE_URI=sqlite:////data/app.db  -e BOX_REPOSITORY_IPN=http://drop.bsf-intranet.org/olip/conf/        bibliosansfrontieres/olip-api-i386`
2. `docker run -d --name olip-dashboard -e API_URL=http://172.17.0.2:5002 bibliosansfrontieres/olip-dashboard-i386` . `API_URL` must use the ip of the `olip-api` container

3. Run kolibri , setting before an environment variable called `API_URL` with the value `http://olip-api:5002/oauth` being `olip-api` the value of the olip-api container ip


Discussion:
# [Codecov](https://codecov.io/gh/learningequality/kolibri/pull/5625?src=pr&el=h1) Report
> Merging [#5625](https://codecov.io/gh/learningequality/kolibri/pull/5625?src=pr&el=desc) into [develop](https://codecov.io/gh/learningequality/kolibri/commit/96648d239da3b60fc90352a2e9d01d96d55a4e7d?src=pr&el=desc) will **decrease** coverage by `0.08%`.
> The diff coverage is `0%`.

| [Impacted Files](https://codecov.io/gh/learningequality/kolibri/pull/5625?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [kolibri/plugins/openid\_connect/hooks.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L2hvb2tzLnB5) | `0% <0%> (ø)` | |
| [.../plugins/openid\_connect/openid\_connect\_settings.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L29wZW5pZF9jb25uZWN0X3NldHRpbmdzLnB5) | `0% <0%> (ø)` | |
| [kolibri/plugins/openid\_connect/auth.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L2F1dGgucHk=) | `0% <0%> (ø)` | |
| [kolibri/plugins/openid\_connect/views.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L3ZpZXdzLnB5) | `0% <0%> (ø)` | |
| [kolibri/plugins/openid\_connect/urls.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L3VybHMucHk=) | `0% <0%> (ø)` | |
| [kolibri/plugins/openid\_connect/kolibri\_plugin.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L2tvbGlicmlfcGx1Z2luLnB5) | `0% <0%> (ø)` | |
| [kolibri/utils/env.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS91dGlscy9lbnYucHk=) | `87.5% <0%> (-6.25%)` | :arrow_down: |
| [...olibri/core/content/utils/import\_export\_content.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9jb3JlL2NvbnRlbnQvdXRpbHMvaW1wb3J0X2V4cG9ydF9jb250ZW50LnB5) | `81.03% <0%> (-5.18%)` | :arrow_down: |
| [kolibri/core/logger/utils/handler.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9jb3JlL2xvZ2dlci91dGlscy9oYW5kbGVyLnB5) | `91.48% <0%> (-4.26%)` | :arrow_down: |
| [kolibri/core/test/\_\_init\_\_.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9jb3JlL3Rlc3QvX19pbml0X18ucHk=) | | |
| ... and [14 more](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree-more) | |

# [Codecov](https://codecov.io/gh/learningequality/kolibri/pull/5625?src=pr&el=h1) Report
> Merging [#5625](https://codecov.io/gh/learningequality/kolibri/pull/5625?src=pr&el=desc) into [develop](https://codecov.io/gh/learningequality/kolibri/commit/a618e8740909f30de65e2e0d8aa484ce07a189f0?src=pr&el=desc) will **decrease** coverage by `0.04%`.
> The diff coverage is `51.72%`.

| [Impacted Files](https://codecov.io/gh/learningequality/kolibri/pull/5625?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [kolibri/utils/cli.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS91dGlscy9jbGkucHk=) | `66.77% <ø> (ø)` | :arrow_up: |
| [kolibri/utils/conf.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS91dGlscy9jb25mLnB5) | `85% <ø> (ø)` | :arrow_up: |
| [kolibri/plugins/user/kolibri\_plugin.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL3VzZXIva29saWJyaV9wbHVnaW4ucHk=) | `100% <ø> (ø)` | :arrow_up: |
| [...gins/openid\_connect/assets/src/views/OIDCLogin.vue](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L2Fzc2V0cy9zcmMvdmlld3MvT0lEQ0xvZ2luLnZ1ZQ==) | `0% <0%> (ø)` | |
| [...plugins/user/assets/src/views/SignInPage/index.vue](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL3VzZXIvYXNzZXRzL3NyYy92aWV3cy9TaWduSW5QYWdlL2luZGV4LnZ1ZQ==) | `50% <100%> (+1.06%)` | :arrow_up: |
| [kolibri/plugins/openid\_connect/kolibri\_plugin.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L2tvbGlicmlfcGx1Z2luLnB5) | `100% <100%> (ø)` | |
| [kolibri/plugins/openid\_connect/api\_urls.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L2FwaV91cmxzLnB5) | `100% <100%> (ø)` | |
| [.../plugins/openid\_connect/openid\_connect\_settings.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L29wZW5pZF9jb25uZWN0X3NldHRpbmdzLnB5) | `100% <100%> (ø)` | |
| [kolibri/plugins/openid\_connect/auth.py](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9wbHVnaW5zL29wZW5pZF9jb25uZWN0L2F1dGgucHk=) | `23.4% <23.4%> (ø)` | |
| [kolibri/core/assets/src/utils/loginComponents.js](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree#diff-a29saWJyaS9jb3JlL2Fzc2V0cy9zcmMvdXRpbHMvbG9naW5Db21wb25lbnRzLmpz) | `50% <50%> (ø)` | |
| ... and [9 more](https://codecov.io/gh/learningequality/kolibri/pull/5625/diff?src=pr&el=tree-more) | |

@indirectlylit @rtibbles I think the code is ready to review now.
I would like to know your opinion + ideas on how we could make it work removing the added line at `kolibri/utils/conf.py` . This is the only line that avoid considering the code a clean plugin, as it affects to the kolibri core code. 
If we could set this as an option in `option.ini` or through an environment variable, this PR could be merged with the current Kolibri develop code without causing any damage.

Resolving patch:
diff --git a/kolibri/plugins/hooks.py b/kolibri/plugins/hooks.py
--- a/kolibri/plugins/hooks.py
+++ b/kolibri/plugins/hooks.py
@@ -276,6 +276,7 @@ def add_hook_to_registries(cls):
                     isabstract(parent)
                     and issubclass(parent, KolibriHook)
                     and parent is not KolibriHook
+                    and hasattr(parent, "_registered_hooks")
                 ):
                     parent.add_hook_to_class_registry(hook)
 
diff --git a/kolibri/plugins/utils/settings.py b/kolibri/plugins/utils/settings.py
--- a/kolibri/plugins/utils/settings.py
+++ b/kolibri/plugins/utils/settings.py
@@ -23,7 +23,12 @@ def _validate_settings_module(settings_module):
     return settings_module
 
 
-_tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS", "LOCALE_PATHS")
+_tuple_settings = (
+    "INSTALLED_APPS",
+    "TEMPLATE_DIRS",
+    "LOCALE_PATHS",
+    "AUTHENTICATION_BACKENDS",
+)
 
 
 def _validate_module_setting(


Test patch: