Skip to content

Commit

Permalink
Merge branch 'main' into fix/resource-rename-flow
Browse files Browse the repository at this point in the history
  • Loading branch information
byewokko committed Aug 8, 2023
2 parents 6913d98 + 0bdb6ad commit d97537d
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 27 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ jobs:
python -m pip install --upgrade pip
pip install bson
pip install pymongo
pip install jwcrypto
pip install git+https://github.com/TeskaLabs/asab.git#egg=asab[encryption]
- name: Test with unittest
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### Features
- Seacat Auth listens on ports 3081 and 8900 by default (#230, PLUM Sprint 230714)
- Add SSL and API key support in ELK batman (#241, GREY Sprint 230714)

---

Expand Down
124 changes: 124 additions & 0 deletions docs/reference/clients.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
title: Clients
---

# Clients

A Client is an entity that uses authentication and authorization services provided by the Authorization Server.
This authorization enables the Client to access protected resources.
In a common scenario, the Client is a browser application and the Resource Owner is a backend application
located on a remote server.

Before a Client can ask for authorization, it must get registered at the Authorization Server and obtain a unique ID.
The registration can either be done in Admin UI or via the Admin API.

## Client metadata

The actual list of client metadata supported by Seacat Auth can be obtained at `GET /client/features` API.

### Canonical OAuth 2.0 and OpenID Connect metadata

Defined in [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html)
and [OAuth 2.0 Dynamic Client Registration Protocol](https://www.rfc-editor.org/rfc/rfc7591#section-2).

#### `client_id`
`NOT EDITABLE` Unique ID of the Client. By default, it is an opaque string generated by the Authorization Server.
It is possible to request a custom ID by supplying the non-canonical `preferred_client_id` parameter in the
client registration request.
The ID is not editable once the client is already registered.

#### `client_name`
`REQUIRED` Human-palatable name of the Client to be presented to the End-User.

#### `client_secret`
OAuth 2.0 client secret string. This value is used by confidential clients to authenticate to the token endpoint.
It is generated by the Authorization Server and is not directly editable.

#### `client_uri`
URL of the home page of the Client.

#### `redirect_uris`
`REQUIRED` Array of Redirection URI values used by the Client.

#### `application_type`
Kind of the application. The default, if omitted, is `web`.

Supported options: `web`

#### `response_types`
JSON array containing a list of the OAuth 2.0 response_type values that the Client is declaring that it will restrict
itself to using. If omitted, the default is that the Client will use only the `code` Response Type.

Supported options: `code`

#### `grant_types`
JSON array containing a list of the OAuth 2.0 Grant Types that the Client is declaring that it will restrict itself
to using. If omitted, the default is that the Client will use only the `authorization_code` Grant Type.

Supported options: `authorization_code`

#### `token_endpoint_auth_method`
Requested Client Authentication method for the Token Endpoint. If omitted, the default is `none`.

Supported options: `none`

#### `code_challenge_method`
Code Challenge Method (used in [Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636))
that the Client will restrict itself to use at the Authorize Endpoint. The default, if omitted, is `none`.

Supported options:

- `none`: PKCE is disabled.
- `plain`: Code challenge is the same as the code verifier.
- `S256`: Code challenge is derived from the code verifier by using SHA-256 hashing algorithm.


## Non-canonical metadata

These are Seacat-Auth-specific features.

#### `preferred_client_id`
`REGISTRATION ONLY` Requests a specific `client_id` instead of a randomly generated one at client registration.

#### `redirect_uri_validation_method`
Specifies the method how the redirect URI used in authorization requests is validated. The default and recommended
value is `full_match`.

Supported options:

- `full_match`: **The only OAuth2.0 compliant option.** Requested Redirect URI must exactly match one of the registered
Redirect URIs.
- `prefix_match`: Requested Redirect URI must start with one of the registered Redirect URIs and their hostname must
exactly match.
- `none`: There is no Redirect URI validation. Not secure.

#### `cookie_name`
`NOT EDITABLE` Unique cookie name derived from Client ID by the Authorization Server. Cookie with this name holds
the information

#### `cookie_webhook_uri`
Webhook URI for setting additional custom cookies at the cookie entrypoint. It must be a back-channel URI and must
accept a JSON PUT request and respond with a JSON object of cookies to set.

#### `cookie_entry_uri`
Public URI of the client's cookie entrypoint. This field is **required** for cookie-based authorization (including
batman authorization). One such entrypoint should be available on every hostname where there are Clients that use
cookie-based authorization.

#### `cookie_domain`
Domain of the client cookie. If not specified, the application's default cookie domain is used.

#### `authorize_uri`
URL of OAuth authorize endpoint. Useful when logging in from different than the default domain.

#### `login_uri`
URL of preferred login page. Useful when logging in from different than the default domain.

#### `authorize_anonymous_users`
Boolean value specifying whether to authorize requests with anonymous users.

#### `anonymous_cid`
ID of credentials that is used for authenticating anonymous sessions.

#### `session_expiration`
Client session expiration. The value can be either the number of seconds or a time-unit string such as `4 h` or `3 d`.
69 changes: 47 additions & 22 deletions seacatauth/batman/elk.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import ssl
import logging
import typing
import aiohttp
Expand Down Expand Up @@ -26,8 +27,13 @@ class ELKIntegration(asab.config.Configurable):

ConfigDefaults = {
"url": "http://localhost:9200/",
"username": "elastic",
"password": "elastic",
# Credentials/api key (mutualy exclusive)
"username": "",
"password": "",
"api_key": "",

# Certs
"ca_file": "",

# List of elasticsearch system users
# If Seacat Auth has users with one of these usernames, it will not sync them
Expand All @@ -54,10 +60,27 @@ def __init__(self, batman_svc, config_section_name="batman:elk", config=None):

username = self.Config.get("username")
password = self.Config.get("password")
api_key = self.Config.get("api_key")
if username != "" and api_key != "":
raise ValueError("Cannot authenticate with both 'api_key' and 'username'+'password'.")
if username != "":
self.Authorization = aiohttp.BasicAuth(username, password)
self.Headers = {
"Authorization": aiohttp.BasicAuth(username, password).encode()
}
elif api_key != "":
self.Headers = {
"Authorization": "ApiKey {}".format(api_key)
}
else:
self.Authorization = None
self.Headers = None

# Prep for SSL
ca_cert = self.Config.get("ca_file")
self.SSLContext = None
if ca_cert != "":
self.SSLContext = ssl.create_default_context(cafile=ca_cert)
self.SSLContext.check_hostname = True
self.SSLContext.verify_mode = ssl.CERT_REQUIRED

self.URL = self.Config.get("url").rstrip("/")
self.ResourcePrefix = self.Config.get("resource_prefix")
Expand Down Expand Up @@ -88,13 +111,14 @@ async def _initialize_resources(self):
"""
# Fetch ELK roles
try:
async with aiohttp.ClientSession(auth=self.Authorization) as session:
async with session.get("{}/_xpack/security/role".format(self.URL)) as resp:
if resp.status != 200:
text = await resp.text()
L.error("Failed to fetch ElasticSearch roles:\n{}".format(text[:1000]))
return
elk_roles_data = await resp.json()
async with aiohttp.TCPConnector(ssl=self.SSLContext or False) as conn:
async with aiohttp.ClientSession(connector=conn, headers=self.Headers) as session:
async with session.get("{}/_xpack/security/role".format(self.URL)) as resp:
if resp.status != 200:
text = await resp.text()
L.error("Failed to fetch ElasticSearch roles:\n{}".format(text[:1000]))
return
elk_roles_data = await resp.json()
except Exception as e:
L.error("Communication with ElasticSearch produced {}: {}".format(type(e).__name__, str(e)))
return
Expand Down Expand Up @@ -179,17 +203,18 @@ async def sync(self, cred: dict, elk_resources: typing.Iterable):
json["roles"] = list(elk_roles)

try:
async with aiohttp.ClientSession(auth=self.Authorization) as session:
async with session.post("{}/_xpack/security/user/{}".format(self.URL, username), json=json) as resp:
if resp.status == 200:
# Everything is alright here
pass
else:
text = await resp.text()
L.warning(
"Failed to create/update user in ElasticSearch:\n{}".format(text[:1000]),
struct_data={"cid": cred["_id"]}
)
async with aiohttp.TCPConnector(ssl=self.SSLContext) as conn:
async with aiohttp.ClientSession(connector=conn, headers=self.Headers) as session:
async with session.post("{}/_xpack/security/user/{}".format(self.URL, username), json=json) as resp:
if resp.status == 200:
# Everything is alright here
pass
else:
text = await resp.text()
L.warning(
"Failed to create/update user in ElasticSearch:\n{}".format(text[:1000]),
struct_data={"cid": cred["_id"]}
)
except Exception as e:
L.error(
"Communication with ElasticSearch produced {}: {}".format(type(e).__name__, str(e)),
Expand Down
2 changes: 1 addition & 1 deletion seacatauth/communication/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#


class CommunicationProviderABC(asab.ConfigObject, abc.ABC):
class CommunicationProviderABC(asab.Configurable, abc.ABC):

Channel = None

Expand Down
2 changes: 1 addition & 1 deletion seacatauth/communication/builders/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#


class MessageBuilderABC(asab.ConfigObject, abc.ABC):
class MessageBuilderABC(asab.Configurable, abc.ABC):
"""
Constructs a message object (dictionary)
"""
Expand Down
2 changes: 1 addition & 1 deletion seacatauth/credentials/providers/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#


class CredentialsProviderABC(asab.ConfigObject, abc.ABC):
class CredentialsProviderABC(asab.Configurable, abc.ABC):

Type = "abc"
Editable = False
Expand Down
2 changes: 1 addition & 1 deletion seacatauth/external_login/providers/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#


class GenericOAuth2Login(asab.ConfigObject):
class GenericOAuth2Login(asab.Configurable):
"""
Generic OAuth2 login provider
Expand Down
2 changes: 1 addition & 1 deletion seacatauth/tenant/providers/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import asab


class TenantsProviderABC(asab.ConfigObject, abc.ABC):
class TenantsProviderABC(asab.Configurable, abc.ABC):


def __init__(self, provider_id, config_section_name, config=None):
Expand Down

0 comments on commit d97537d

Please sign in to comment.