Adjust output of claim maps for better parsing (#2566)

* Adjust output of claim maps for better parsing
* Update python tests for OAuth2 bits
* fixing workflows for container builds

---------

Co-authored-by: James Hodgkinson <james@terminaloutcomes.com>
This commit is contained in:
Firstyear 2024-02-26 13:33:32 +10:00 committed by GitHub
parent 1a6400b58e
commit adb575947f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 120 additions and 20 deletions

View file

@ -12,9 +12,22 @@ concurrency:
cancel-in-progress: true
jobs:
set_lower_case_name:
runs-on: ubuntu-latest
name: set lower case owner name
steps:
- id: step1
run: |
echo "OWNER_LC=${OWNER,,}" >> "${GITHUB_OUTPUT}"
env:
OWNER: '${{ github.repository_owner }}'
outputs:
owner_lc: ${{ steps.step1.outputs.OWNER_LC }}
kanidm_build:
name: Build kanidm Docker image
runs-on: ubuntu-latest
needs: set_lower_case_name
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
@ -23,7 +36,8 @@ jobs:
uses: docker/build-push-action@v5
with:
platforms: "linux/amd64"
tags: ghcr.io/${{ github.repository_owner }}/kanidm:devel
tags: ghcr.io/${{ needs.set_lower_case_name.outputs.owner_lc }}/kanidm:devel
build-args: |
"KANIDM_FEATURES="
# "KANIDM_BUILD_OPTIONS=-j1"

View file

@ -12,9 +12,22 @@ concurrency:
cancel-in-progress: true
jobs:
set_lower_case_name:
runs-on: ubuntu-latest
name: set lower case owner name
steps:
- id: step1
run: |
echo "OWNER_LC=${OWNER,,}" >> "${GITHUB_OUTPUT}"
env:
OWNER: '${{ github.repository_owner }}'
outputs:
owner_lc: ${{ steps.step1.outputs.OWNER_LC }}
kanidmd_build:
name: Build kanidmd Docker image
runs-on: ubuntu-latest
needs: set_lower_case_name
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
@ -41,7 +54,7 @@ jobs:
uses: docker/build-push-action@v5
with:
platforms: "linux/amd64"
tags: ghcr.io/${{ github.repository_owner }}/kanidmd:devel
tags: ghcr.io/${{ needs.set_lower_case_name.outputs.owner_lc }}/kanidmd:devel
# build-args: |
# "KANIDM_BUILD_OPTIONS=-j1"
file: server/Dockerfile

View file

@ -1,11 +1,37 @@
# pylint: disable=too-few-public-methods
# ^ disabling this because pydantic models don't have public methods
import json
from typing import Dict, List, TypedDict
from pydantic import BaseModel, ConfigDict, RootModel
class OAuth2RsClaimMap(BaseModel):
name: str
group: str
join: str
values: List[str]
@classmethod
def from_entry(cls, entry: str) -> "OAuth2RsClaimMap":
name, group, join, values = entry.split(":")
values = json.loads(values).split(",")
return cls(name=name, group=group, join=join, values=values)
class OAuth2RsScopeMap(BaseModel):
group: str
values: List[str]
@classmethod
def from_entry(cls, entry: str) -> "OAuth2RsScopeMap":
group, values = entry.split(":")
values = values.replace("{", "[").replace("}", "]")
values = json.loads(values.strip())
return cls(group=group, values=values)
class OAuth2Rs(BaseModel):
classes: List[str]
displayname: str
@ -14,7 +40,9 @@ class OAuth2Rs(BaseModel):
oauth2_rs_basic_secret: str
oauth2_rs_origin: str
oauth2_rs_token_key: str
oauth2_rs_sup_scope_map: List[str]
oauth2_rs_scope_map: List[OAuth2RsScopeMap]
oauth2_rs_sup_scope_map: List[OAuth2RsScopeMap]
oauth2_rs_claim_map: List[OAuth2RsClaimMap]
class RawOAuth2Rs(BaseModel):
@ -38,6 +66,19 @@ class RawOAuth2Rs(BaseModel):
if len(self.attrs[field]) == 0:
raise ValueError(f"Empty field {field} in {self.attrs}")
oauth2_rs_scope_map = [
OAuth2RsScopeMap.from_entry(entry)
for entry in self.attrs.get("oauth2_rs_scope_map", [])
]
oauth2_rs_sup_scope_map = [
OAuth2RsScopeMap.from_entry(entry)
for entry in self.attrs.get("oauth2_rs_sup_scope_map", [])
]
oauth2_rs_claim_map = [
OAuth2RsClaimMap.from_entry(entry)
for entry in self.attrs.get("oauth2_rs_claim_map", [])
]
return OAuth2Rs(
classes=self.attrs["class"],
displayname=self.attrs["displayname"][0],
@ -46,9 +87,12 @@ class RawOAuth2Rs(BaseModel):
oauth2_rs_basic_secret=self.attrs["oauth2_rs_basic_secret"][0],
oauth2_rs_origin=self.attrs["oauth2_rs_origin"][0],
oauth2_rs_token_key=self.attrs["oauth2_rs_token_key"][0],
oauth2_rs_sup_scope_map=self.attrs.get("oauth2_rs_sup_scope_map", []),
oauth2_rs_scope_map=oauth2_rs_scope_map,
oauth2_rs_sup_scope_map=oauth2_rs_sup_scope_map,
oauth2_rs_claim_map=oauth2_rs_claim_map,
)
Oauth2RsList = RootModel[List[RawOAuth2Rs]]

View file

@ -61,7 +61,7 @@ load-plugins = "pylint_pydantic,pylint_pytest"
[tool.ruff]
line-length = 150
[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
"tests/*.py" = [
"F401", # unused import, reused fixtures across all tests
"F811", # pytest fixtures

View file

@ -1,5 +1,6 @@
import json
import logging
import os
from pathlib import Path
from kanidm import KanidmClient
@ -23,9 +24,12 @@ async def test_oauth2_rs_list(client: KanidmClient) -> None:
logging.basicConfig(level=logging.DEBUG)
print(f"config: {client.config}")
username = "admin"
# change this to be your admin password.
password = "pdf1Xz8q2QFsMTsvbv2jXNBaSEsDpW9h83ZRsH7dDfsJeJdM"
username = "idm_admin"
# change this to be the password.
password = os.getenv("KANIDM_PASSWORD")
if password is None:
print("No KANIDM_PASSWORD env var set for testing")
raise pytest.skip("No KANIDM_PASSWORD env var set for testing")
auth_resp = await client.authenticate_password(
username, password, update_internal_auth_token=True
@ -41,13 +45,9 @@ async def test_oauth2_rs_list(client: KanidmClient) -> None:
resource_servers = await client.oauth2_rs_list()
print("content:")
print(json.dumps(resource_servers, indent=4))
if resource_servers:
for oauth_rs in resource_servers:
print(json.dumps(oauth_rs.model_dump(), indent=4, default=str))
for mapping in oauth_rs.oauth2_rs_sup_scope_map:
print(f"oauth2_rs_sup_scope_map: {mapping}")
user, scopes = mapping.split(":")
scopes = scopes.replace("{", "[").replace("}", "]")
scopes = json.loads(scopes)
print(f"{user=} {scopes=}")

View file

@ -126,6 +126,9 @@ ${KANIDM} system oauth2 update-scope-map "${OAUTH2_RP_ID}" "${TEST_GROUP}" openi
echo "Creating the ${OAUTH2_RP_ID} OAuth2 RP Supplemental Scope Map"
${KANIDM} system oauth2 update-sup-scope-map "${OAUTH2_RP_ID}" "${TEST_GROUP}" admin -D "${IDM_ADMIN_USER}"
echo "Creating a claim map for RS ${OAUTH2_RP_ID}"
${KANIDM} system oauth2 update-claim-map "${OAUTH2_RP_ID}" testclaim "${TEST_GROUP}" foo bar
echo "Creating the OAuth2 RP Secondary Supplemental Crab-baite Scope Map.... wait, no that's not a thing."
echo "Checking the OAuth2 RP Exists"

View file

@ -764,6 +764,27 @@ pub trait QueryServerTransaction<'a> {
})
.collect();
v
} else if let Some(r_map) = value.as_oauthclaim_map() {
let mut v = Vec::new();
for (claim_name, mapping) in r_map.iter() {
for (group_ref, claims) in mapping.values() {
let join_char = mapping.join().to_char();
let nv = self.uuid_to_spn(*group_ref)?;
let resolved_id = match nv {
Some(v) => v.to_proto_string_clone(),
None => uuid_to_proto_string(*group_ref),
};
let joined = str_concat!(claims, ',');
v.push(format!(
"{}:{}:{}:{:?}",
claim_name, resolved_id, join_char, joined
))
}
}
Ok(v)
} else {
let v: Vec<_> = value.to_proto_string_clone_iter().collect();
Ok(v)

View file

@ -1000,6 +1000,17 @@ pub enum OauthClaimMapJoin {
JsonArray,
}
impl OauthClaimMapJoin {
pub(crate) fn to_char(&self) -> char {
match self {
OauthClaimMapJoin::CommaSeparatedValue => ',',
OauthClaimMapJoin::SpaceSeparatedValue => ' ',
// Should this be something else?
OauthClaimMapJoin::JsonArray => ';',
}
}
}
impl From<DbValueOauthClaimMapJoinV1> for OauthClaimMapJoin {
fn from(value: DbValueOauthClaimMapJoinV1) -> OauthClaimMapJoin {
match value {

View file

@ -368,7 +368,6 @@ pub trait ValueSetT: std::fmt::Debug + DynClone {
}
fn as_oauthclaim_map(&self) -> Option<&BTreeMap<String, OauthClaimMapping>> {
debug_assert!(false);
None
}

View file

@ -637,12 +637,7 @@ impl ValueSetT for ValueSetOauthClaimMap {
fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
Box::new(self.map.iter().flat_map(|(name, mapping)| {
mapping.values.iter().map(move |(group, claims)| {
let join_char = match mapping.join {
OauthClaimMapJoin::CommaSeparatedValue => ',',
OauthClaimMapJoin::SpaceSeparatedValue => ' ',
// Should this be something else?
OauthClaimMapJoin::JsonArray => ';',
};
let join_char = mapping.join.to_char();
let joined = str_concat!(claims, join_char);