Skip to main content

ahri_tre_pgmeta/
workflow_entry.rs

1//! PostgreSQL implementation of governed ORCID workflow entry provisioning.
2
3use crate::{PgMetaError, PgMetadataConnection};
4
5/// Creates or reuses the requester's login role and grants inheriting
6/// membership in the configured OAuth group role.
7pub fn provision_orcid_workflow_entry(
8    connection: &mut PgMetadataConnection,
9    requester_orcid: &str,
10    oauth_group_role: &str,
11) -> Result<String, PgMetaError> {
12    let requester_orcid = requester_orcid.trim();
13    let oauth_group_role = oauth_group_role.trim();
14    if requester_orcid.is_empty() || oauth_group_role.is_empty() {
15        return Err(PgMetaError::Decode {
16            field: "workflow_entry",
17            value: "<redacted>".to_string(),
18            message: "requester ORCID and OAuth group role must not be empty".to_string(),
19        });
20    }
21    let requester_role = normalize_orcid_role(requester_orcid);
22    connection
23        .client()
24        .batch_execute(&membership_sql(&requester_role, oauth_group_role))
25        .map_err(|source| PgMetaError::Write {
26            operation: "provision ORCID workflow entry",
27            source,
28        })?;
29    Ok(requester_role)
30}
31
32fn normalize_orcid_role(orcid: &str) -> String {
33    let orcid = orcid.trim();
34    if orcid.starts_with("orcid_") {
35        orcid.to_string()
36    } else {
37        format!("orcid_{orcid}")
38    }
39}
40
41fn membership_sql(requester_role: &str, oauth_group_role: &str) -> String {
42    format!(
43        "DO $$\nBEGIN\n    IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = {role_name_literal}) THEN\n        EXECUTE format('CREATE ROLE %I LOGIN', {role_name_literal});\n    END IF;\n    EXECUTE format('GRANT %I TO %I WITH INHERIT TRUE', {group_role_literal}, {role_name_literal});\nEND\n$$;",
44        role_name_literal = sql_literal(requester_role),
45        group_role_literal = sql_literal(oauth_group_role),
46    )
47}
48
49fn sql_literal(value: &str) -> String {
50    format!("'{}'", value.replace('\'', "''"))
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn normalizes_requester_and_requires_inheriting_membership() {
59        assert_eq!(
60            normalize_orcid_role(" 0000-0000-0000-0001 "),
61            "orcid_0000-0000-0000-0001"
62        );
63        assert_eq!(
64            normalize_orcid_role("orcid_0000-0000-0000-0001"),
65            "orcid_0000-0000-0000-0001"
66        );
67        let sql = membership_sql("orcid_0000-0000-0000-0001", "tre-oauth");
68        assert!(sql.contains("CREATE ROLE %I LOGIN"));
69        assert!(sql.contains("GRANT %I TO %I WITH INHERIT TRUE"));
70    }
71}