diff --git a/README.md b/README.md index 06982f6c059a5ba618042154d628821a6fb5c828..81ceacc918feeb275b6dd2db82a442fc9a40f447 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,30 @@ # Install -> THIS IS JUST A DEMO REPO. IT WORKS ON LOCALHOST ONLY - -Clone the repo and make sure to also fetch the submodules. -``` -git submodule init -git submodule update -``` -Refer to the [hydra install guide](https://www.ory.sh/docs/next/hydra/5min-tutorial) to get the demo running. Change the following values in the quickstart.yml file before starting hydra with docker-compose. - -``` - - URLS_SELF_ISSUER=http://oas.example.net:4444 - - URLS_CONSENT=http://127.0.0.1:5001/consent - - URLS_LOGIN=http://127.0.0.1:5000/login - - URLS_LOGOUT=http://127.0.0.1:5000/logout - - DSN=memory - - SECRETS_SYSTEM=youReallyNeedToChangeThis - - OIDC_SUBJECT_TYPES_SUPPORTED=public,pairwise - - OIDC_SUBJECT_TYPE_PAIRWISE_SALT=youReallyNeedToChangeThis - restart: unless-stopped - - consent: - environment: - - HYDRA_ADMIN_URL=http://127.0.0.1:4445 -``` - -After Hydra is up and running, start the login provider -``` -cd login_provider -source .env/bin/activate -pip3 install -r requirements.txt -flask run --port 5000 -``` -and the consent provider -``` -cd consent_provider -source .env/bin/activate -pip3 install -r requirements.txt -flask run --port 5001 -``` +Installation should be done via the helm using the helmchart contained in `./helmchart`. +Make sure to edit the values in `./helmchart/values.yaml` according to your needs # Using SSO -To use SSO configure your oAuth client (for example netxtcloud) and create a new oAuth client object. To create the client object, please also refer to the ORY Hydra documentation. -To configure your client refer to `127.0.0.1:4444/.well-known/openid-configuration`. +To use OpenID Connect or oAuth you need to set up an oAuth Client for every application that +needs to authenticate it's users. You can leverage the Hydra Admin API to create oAuth clients. +As a starting point, you can have a look at the script provided in `test/`. -The username and password of the demo user can be found in the login_provider app.py file. +To use SSO configure your oAuth client (for example netxtcloud) and create a new oAuth client object. +Refer to `https://sso.oas.example.net/.well-known/openid-configuration` as a reference on how to configure your openID Connect or oAuth client + +# Testing + +In order to run tests locally, you can start the environment via `docker-compose`. +To make the test setup work on your machine, install docker and docker-compose and edit +the `docker-compose.yml` file. Make sure all of the URLS listed in the environment of the hydra +service are accessible by your agent (usually your browser) and the application that will act +as an oAuth / OpenID Connect client (for example nextcloud). + +Notice that you need to create users and applications before being able to login. +You can use the scripts located in `user-panel/utils` to create users for testing. + +If you don't have a test application yourself, you can use the small OpeinID Connect +test application located at `test/login_logout/` + +Also refer to `.gitlab-ci.yaml` to get an idea on how to run all of the tests that are +contained in this repository. diff --git a/consent_provider/app.py b/consent_provider/app.py index 177ca4ea39e26df64b5f216498a1d55cf6141af5..8cc51ab3e3381c8314226ba5b160e1d04b484d34 100644 --- a/consent_provider/app.py +++ b/consent_provider/app.py @@ -10,6 +10,20 @@ app = Flask(__name__) @app.route('/', methods=['GET']) def home(): + """Checks user app permission + + Checks user app permission, by loading a consent object via the Hydra admin API and + validating, that the user triggering the request has sufficient permissions by querying + the GraphQL API. If the user is allowed to use the app the request is accepted and openID + claims are sent to Hydra. + + Args: + consent_challenge: Reference to a consent challenge object that can be requested via the Hydra + Admin API (GET) + + Returns: + Redirect to the url that is provided by the consent challenge object. + """ hydra = HydraAdmin(HYDRA_ADMIN_URL) challenge = request.args.get("consent_challenge") if not challenge: diff --git a/login_provider/app.py b/login_provider/app.py index ba85dcde002050cfeeb980d0366d86450c5dcb5c..3d60993bfd7395f7252af546765bec6960c863b9 100644 --- a/login_provider/app.py +++ b/login_provider/app.py @@ -26,6 +26,20 @@ def user_loader(username): @app.route('/', methods=['GET']) @login_required def home(): + """Accepts hydra login challenge or renders welcome page + + Connects to the Hydra admin API to accept the login challenge. + A reference to this challenge object is passed with via args.login_challenge (GET) + + Args: + login_challenge: A alphanumeric id generated by Hydra, that references a login + challenge object. The login challenge object can be rejected or accepted via the + hydra admin API + + Returns: + Redirect that is saved in the challenge object + If no challenge reference was passed this function renders a welcome page + """ logout_form = LogoutForm() challenge = request.args.get("login_challenge") if not challenge: @@ -36,6 +50,16 @@ def home(): @app.route('/login', methods=['GET', 'POST']) def login(): + """Provides login form and handles Login attempt + + Args: + login_form: contains login data submitted by a user (POST) + next_url: url that this function redirects to after logging in the user + + Returns: + Error page in case the login was unsuccessful + Redirect to home page, forwarding the login_challenge in case login was successful + """ login_form = LoginForm() if login_form.validate_on_submit(): user = User(login_form.username.data) @@ -49,7 +73,6 @@ def login(): return render_template('login.html', login_form=login_form) def is_safe_url(url): - print(url) safe = True if url == "" else False safe = True if url == "/" or safe else False safe = True if url[:18] == "/?login_challenge=" \ diff --git a/login_provider/db.py b/login_provider/db.py index 7a0d456c559095dd6b49a4913fa75549a9a34139..3eff8b649b4e93f3888e1aba31949e8518df9326 100644 --- a/login_provider/db.py +++ b/login_provider/db.py @@ -15,6 +15,7 @@ class User(UserMixin): self._load_remote_user_info() def _load_remote_user_info(self): + """Loads userdata from remote GraphQL API""" querystring = '''{{ getUser(username: "{0}"){{ email, @@ -26,6 +27,18 @@ class User(UserMixin): self.email = result["data"]["getUser"]["email"] def _verify_password(self, password): + """Verifies cleartext password + + Sends the cleartext password provided to the function to the GraphQL API + which verifies the password by hashing it and comparing it to a stored password + hash. + + Args: + password: cleartext password + + Returns: + Boolean result of password verification + """ querystring = '''{{ verifyPassword( username: "{0}",