Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d60f8b9a3 | ||
|
|
f36d88a69c | ||
|
|
ef46b463e4 | ||
|
|
4818c5a26f | ||
|
|
7106543089 |
@@ -77,30 +77,46 @@ jobs:
|
|||||||
tag="${{ github.ref_name }}"
|
tag="${{ github.ref_name }}"
|
||||||
ver="${tag#v}"
|
ver="${tag#v}"
|
||||||
host="${{ github.server_url }}"
|
host="${{ github.server_url }}"
|
||||||
rm -rf iris-src
|
remote="https://${IRIS_PUSH_USER}:${IRIS_PUSH_TOKEN}@${host#https://}/uhhm/iris.git"
|
||||||
git clone -q "$host/uhhm/iris.git" iris-src
|
# Re-pin, version, changelog: idempotent, so a retry on a fresh
|
||||||
cd iris-src
|
# clone redoes exactly the same edits.
|
||||||
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
|
match() {
|
||||||
echo "iris $tag already exists, building it"
|
|
||||||
git checkout -q "$tag"
|
|
||||||
else
|
|
||||||
sed -i -E "s|^(portal = \{ git = \"[^\"]+\", tag = \")[^\"]+(\")|\1$tag\2|" Cargo.toml
|
sed -i -E "s|^(portal = \{ git = \"[^\"]+\", tag = \")[^\"]+(\")|\1$tag\2|" Cargo.toml
|
||||||
sed -i -E "0,/^version = \"[^\"]+\"/s//version = \"$ver\"/" Cargo.toml
|
sed -i -E "0,/^version = \"[^\"]+\"/s//version = \"$ver\"/" Cargo.toml
|
||||||
cargo update -q -p portal
|
cargo update -q -p portal
|
||||||
awk -v h="## $ver ($(date -u +%F)) - portal $tag" -v l="- Matches portal $tag (released by portal's publish job)." \
|
awk -v h="## $ver ($(date -u +%F)) - portal $tag" -v l="- Matches portal $tag (released by portal's publish job)." \
|
||||||
'done==0 && /^## /{print h; print ""; print l; print ""; done=1} {print}' CHANGELOG.md > CHANGELOG.new
|
'done==0 && /^## /{print h; print ""; print l; print ""; done=1} {print}' CHANGELOG.md > CHANGELOG.new
|
||||||
mv CHANGELOG.new CHANGELOG.md
|
mv CHANGELOG.new CHANGELOG.md
|
||||||
fi
|
|
||||||
cargo build --release
|
|
||||||
cargo test
|
|
||||||
if ! git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
|
|
||||||
git config user.name "portal release"
|
git config user.name "portal release"
|
||||||
git config user.email "portal@uhhm.no"
|
git config user.email "portal@uhhm.no"
|
||||||
git commit -qam "Match portal $tag"
|
git commit -qam "Match portal $tag"
|
||||||
git tag "$tag"
|
git tag "$tag"
|
||||||
git push -q "https://${IRIS_PUSH_USER}:${IRIS_PUSH_TOKEN}@${host#https://}/uhhm/iris.git" main "$tag"
|
}
|
||||||
echo "pushed iris $tag"
|
# Someone may push to iris main while this job builds; a push
|
||||||
fi
|
# then fails as non-fast-forward, and the answer is to start
|
||||||
|
# over on a fresh clone, once.
|
||||||
|
for attempt in 1 2; do
|
||||||
|
cd "$GITHUB_WORKSPACE"
|
||||||
|
rm -rf iris-src
|
||||||
|
git clone -q "$host/uhhm/iris.git" iris-src
|
||||||
|
cd iris-src
|
||||||
|
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
|
||||||
|
echo "iris $tag already exists, building it"
|
||||||
|
git checkout -q "$tag"
|
||||||
|
cargo build --release
|
||||||
|
cargo test
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
match
|
||||||
|
cargo build --release
|
||||||
|
cargo test
|
||||||
|
if git push -q "$remote" main "$tag"; then
|
||||||
|
echo "pushed iris $tag"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "push rejected on attempt $attempt (iris main moved), retrying on a fresh clone"
|
||||||
|
[ "$attempt" = 2 ] && exit 1
|
||||||
|
done
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
run: |
|
run: |
|
||||||
@@ -131,6 +147,7 @@ jobs:
|
|||||||
auth="Authorization: token ${{ secrets.GITHUB_TOKEN }}"
|
auth="Authorization: token ${{ secrets.GITHUB_TOKEN }}"
|
||||||
# The release notes are this version's section of CHANGELOG.md.
|
# The release notes are this version's section of CHANGELOG.md.
|
||||||
notes=$(awk -v v="${tag#v}" '/^## /{p = ($2 == v)} p' CHANGELOG.md | tail -n +2)
|
notes=$(awk -v v="${tag#v}" '/^## /{p = ($2 == v)} p' CHANGELOG.md | tail -n +2)
|
||||||
|
[ -n "$(printf "%s" "$notes" | tr -d "[:space:]")" ] || { echo "CHANGELOG.md has no section for $tag - write it and re-tag"; exit 1; }
|
||||||
body=$(jq -n --arg tag "$tag" --arg notes "$notes" '{tag_name: $tag, name: $tag, body: $notes}')
|
body=$(jq -n --arg tag "$tag" --arg notes "$notes" '{tag_name: $tag, name: $tag, body: $notes}')
|
||||||
id=$(curl -sf -X POST -H "$auth" -H 'Content-Type: application/json' \
|
id=$(curl -sf -X POST -H "$auth" -H 'Content-Type: application/json' \
|
||||||
-d "$body" "$api/releases" | jq .id) \
|
-d "$body" "$api/releases" | jq .id) \
|
||||||
|
|||||||
+21
-3
@@ -4,6 +4,24 @@ One line per change, grouped by the release that shipped it. Newest
|
|||||||
first. A release is a Cargo.toml bump plus a `v<version>` tag; the
|
first. A release is a Cargo.toml bump plus a `v<version>` tag; the
|
||||||
content repos pin the tag they run (see the end of this file).
|
content repos pin the tag they run (see the end of this file).
|
||||||
|
|
||||||
|
## 0.3.42 (2026-09-23)
|
||||||
|
|
||||||
|
- people: the Kanidm person lookup reads the body, since Kanidm 1.11 answers a missing person with 200 and `null` rather than 404; before this every invite skipped account creation and failed on the group step
|
||||||
|
|
||||||
|
## 0.3.41 (2026-09-23)
|
||||||
|
|
||||||
|
- publish: the iris release step retries on a fresh clone when its push loses a race against a push to iris main (the v0.3.40 tag never published)
|
||||||
|
- iris: `check --access` prints the access matrix, and every check validates the compiled policy against the schema
|
||||||
|
|
||||||
|
## 0.3.40 (2026-09-23)
|
||||||
|
|
||||||
|
- One access policy, compiled from content into Cedar on every load (`src/access.rs`): pages, resource reads, desk transitions, self transitions and the automation endpoint all decide through it; decisions carry rule ids, refusals and mutations publish on `portal.access.decided`, and `portal.access.<site>.may` answers the same question for other apps over NATS
|
||||||
|
- `invite: {group}` on an alternative onboards a person into Kanidm from a desk (`src/people.rs`, `KANIDM_API_TOKEN`), recording the one-time credential link in `portal_invites` and on `portal.people.invited`
|
||||||
|
|
||||||
|
## 0.3.39 (2026-09-23)
|
||||||
|
|
||||||
|
- publish: releases the matching uhhm/iris by proxy - re-pins, builds, tests and tags it as this version (needs the `IRIS_PUSH_USER` variable and `IRIS_PUSH_TOKEN` secret); iris carries portal's version number from here on
|
||||||
|
|
||||||
## 0.3.38 (2026-09-22)
|
## 0.3.38 (2026-09-22)
|
||||||
|
|
||||||
- publish: the iris build step gets a writable cargo home; the v0.3.37 tag never published
|
- publish: the iris build step gets a writable cargo home; the v0.3.37 tag never published
|
||||||
@@ -287,6 +305,6 @@ tarball deploy on kasse.
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| uhhm.no | ergo | `app@uhhm-portal` | v0.3.36 |
|
| uhhm.no | ergo | `app@uhhm-portal` | v0.3.36 |
|
||||||
| redoal.com | ergo | `app@redoal-portal` | v0.3.36 |
|
| redoal.com | ergo | `app@redoal-portal` | v0.3.36 |
|
||||||
| westra.klingenbergbygg.no | kasse | `app@westra-portal` | v0.3.36 |
|
| westra.klingenbergbygg.no | kasse | `app@westra-portal` | v0.3.41 |
|
||||||
| portal.klingenbergbygg.no | kasse | `app@klingenberg-portal` | v0.3.36 |
|
| portal.klingenbergbygg.no | kasse | `app@klingenberg-portal` | v0.3.41 |
|
||||||
| vel.klingenbergbygg.no | kasse | `app@tomtervel-portal` | v0.3.36 |
|
| vel.klingenbergbygg.no | kasse | `app@tomtervel-portal` | v0.3.41 |
|
||||||
|
|||||||
Generated
+1
-1
@@ -3265,7 +3265,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portal"
|
name = "portal"
|
||||||
version = "0.3.40"
|
version = "0.3.42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "portal"
|
name = "portal"
|
||||||
version = "0.3.40"
|
version = "0.3.42"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
+24
-6
@@ -31,6 +31,19 @@ pub struct Invited {
|
|||||||
pub created: bool,
|
pub created: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether a `GET /v1/person/<name>` answer means the person exists.
|
||||||
|
/// Kanidm 1.11 answers a missing person with 200 and the body `null`
|
||||||
|
/// rather than 404, so the status alone would report everyone as
|
||||||
|
/// existing and no account would ever get created. `None` for an
|
||||||
|
/// answer that is neither.
|
||||||
|
fn person_lookup_found(status: u16, body: &str) -> Option<bool> {
|
||||||
|
match status {
|
||||||
|
200 => Some(body.trim() != "null" && !body.trim().is_empty()),
|
||||||
|
404 => Some(false),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Kanidm {
|
pub struct Kanidm {
|
||||||
base: String,
|
base: String,
|
||||||
token: String,
|
token: String,
|
||||||
@@ -60,12 +73,8 @@ impl Kanidm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn person_exists(&self, username: &str) -> anyhow::Result<bool> {
|
async fn person_exists(&self, username: &str) -> anyhow::Result<bool> {
|
||||||
let (status, _) = self.call(openidconnect::reqwest::Method::GET, &format!("/v1/person/{username}"), None).await?;
|
let (status, text) = self.call(openidconnect::reqwest::Method::GET, &format!("/v1/person/{username}"), None).await?;
|
||||||
match status {
|
person_lookup_found(status, &text).ok_or_else(|| anyhow::anyhow!("kanidm: looking up {username}: http {status}"))
|
||||||
200 => Ok(true),
|
|
||||||
404 => Ok(false),
|
|
||||||
other => anyhow::bail!("kanidm: looking up {username}: http {other}"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The whole onboarding, in the order that leaves the least behind
|
/// The whole onboarding, in the order that leaves the least behind
|
||||||
@@ -161,4 +170,13 @@ mod tests {
|
|||||||
assert_eq!(username_for("42@x.no"), "person");
|
assert_eq!(username_for("42@x.no"), "person");
|
||||||
assert_eq!(username_for("Ærlig@x.no"), "rlig");
|
assert_eq!(username_for("Ærlig@x.no"), "rlig");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_null_person_answer_means_nobody_there() {
|
||||||
|
assert_eq!(person_lookup_found(200, "null"), Some(false));
|
||||||
|
assert_eq!(person_lookup_found(200, "null\n"), Some(false));
|
||||||
|
assert_eq!(person_lookup_found(200, r#"{"attrs":{"name":["bl"]}}"#), Some(true));
|
||||||
|
assert_eq!(person_lookup_found(404, ""), Some(false));
|
||||||
|
assert_eq!(person_lookup_found(401, "unauthorized"), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user