diff --git a/src/oauth.rs b/src/oauth.rs index 8e5aa79b391e3827d82bbc07a0f52b9083714bbe..474eb9b988fa1044692e785f308807f43933a8e0 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -151,6 +151,29 @@ fn get_data(conf: &Config, prefix: &str, endpoint: &str, param: String, token: & .text()?) } +/// Retrieve a jq program from the config +/// +/// Arguments +/// --------- +/// +/// `conf` - reference to the config object +/// `prefix` - current prefix (probably `nss` or `pam`) +/// `endpoint` - name of the URL endpoint operating on, e.g. `passwd.list` +/// `multi` - `true` if the nndpoint will return an array +/// `rev` - `true` if looking for the reverse mapping program (see `get_data_jq`) +/// +/// The endpoint will be truncated starting from the right if no program is found under +/// the config key, so that a single jq programm can be configured for `passwd` instead of +/// three separate programs for `passwd.list`, `passwd.by_uid` and `passwd.by_name`. +/// +/// Lookup of the program in the config is done through the general config fetching mechanism +/// (see `get_optional`). +/// +/// The programm will be looked up unter `<prefix>.maps`. If `rev` is true, the programm will +/// be looked up under `maps.rev` instead of `maps`. +/// +/// If `multi` is `true`, the resulting program will be wrapped into jq's `map()` function +/// to be applied to an array of objects. fn get_jq_prog(conf: &Config, prefix: &str, endpoint: &str, multi: bool, rev: bool) -> jq_rs::Result<jq_rs::JqProgram> { // Generate config key to find jq program under let prog_key = match rev { @@ -191,6 +214,83 @@ fn get_jq_prog(conf: &Config, prefix: &str, endpoint: &str, multi: bool, rev: bo jq_rs::compile(&jq_code) } +/// Retrieve JSON data from a configured endpoint, transforming using configured jq programs +/// +/// This function takes a prefix (probably `nss` or `pam`, and an endpoint name to lookup +/// information in the passed configuration. It then uses this information and the passed +/// parameter to construct a URL to retrieve data from the HTTP API. +/// +/// Transformation occurs both for the passed parameter and for the retrieved data. +/// Consider the following scenario: +/// +/// The configured backend server provides three URLs for doing NSS-like lookups: +/// +/// * `https://example.com/users/` - get a JSON array of all users +/// * `https://example.com/user/1234/` - get a single JSON object for a user with a numeric user ID +/// * `https://example.com/user/janedoe/` - get a single JSON object for a user with a user name +/// +/// Any user object returned by the server looks like this: +/// +/// ```json +/// { +/// "username": "janedoe", +/// "uid": 1234, +/// "primary_gid": 100, +/// "home_directory": "/home/janedoe", +/// "login_shell": "/bin/bash", +/// "full_name": "Doe, Jane" +/// } +/// ``` +/// +/// To integrate the information in our system, which also has other user sources, +/// we need to ensure the following: +/// +/// * Numeric user IDs need to be mapped starting at 10000 +/// * Home directories need to be re-mapped to /srv/remote_users +/// * The full name (GECOS) shall be suffixed with "(Remote)" for clarity +/// * We need to set a static password of "x" because the API (correctly) does not serve passwords +/// * All field names need to be renamed to the fields of the passwd struct +/// +/// To accomplish this, we will configure two jq programs: +/// +/// ```toml +/// nss.maps.passwd = """ +/// { +/// name: .username, +/// # No passwords in passwd +/// passwd: "x", +/// # Map user and group IDs starting at 10000 +/// uid: (.uid + 10000), +/// gid: (.primary_gid + 10000), +/// # Append organisation name to Gecos field +/// gecos: (.full_name + " (Remote)"), +/// # Remap /home from server to /srv/teckids locally +/// dir: ("/srv/remote_users/" + (.home_directory|ltrimstr("/home/"))), +/// shell: .login_shell +/// } +/// """ +/// +/// # Reverse mapping to make sure uid lookups on entries mapped above still work +/// nss.maps.rev.passwd.by_uid = ". - 10000" +/// ``` +/// +/// The first program maps result objects into new JSON objects with the rules described +/// inline. The `map()` function from jq that is needed to apply the program to every +/// object in an array will be added automatically if a list is retrieved (`multi` is `true`). +/// +/// The second program is used when a single user is queried by their numeric user ID, and it +/// reverses the transformation done to the uid field so the lookup on the server gets the +/// expected user ID. +/// +/// Along with the following URL configuration, this brings everything into place: +/// +/// ```toml +/// nss.urls.passwd.list = "https://example.com/users/" +/// nss.urls.passwd.by_uid = "https://example.com/users/{}/" +/// nss.urls.passwd.by_name = "https://example.com/users/{}/" +/// ``` +/// +/// In the second and thrid URL, the placeholder `{}` will be replaced with the lookup key. pub fn get_data_jq<T: for<'de> Deserialize<'de>, V: Serialize>(conf: &Config, prefix: &str, endpoint: &str, param: V, token: &BasicTokenResponse, multi: bool) -> Result<T, Box<dyn error::Error>> { // Get jq mapping programs for forward and reverse mappings let mut jq_prog_fwd = get_jq_prog(&conf, prefix, endpoint, multi, false)?;