Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package sso-mib for openSUSE:Factory checked in at 2026-03-18 16:50:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/sso-mib (Old) and /work/SRC/openSUSE:Factory/.sso-mib.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "sso-mib" Wed Mar 18 16:50:18 2026 rev:2 rq:1340875 version:0.8.0 Changes: -------- --- /work/SRC/openSUSE:Factory/sso-mib/sso-mib.changes 2026-03-11 20:52:26.830843533 +0100 +++ /work/SRC/openSUSE:Factory/.sso-mib.new.8177/sso-mib.changes 2026-03-18 16:51:48.676305614 +0100 @@ -1,0 +2,8 @@ +Tue Mar 17 23:50:08 UTC 2026 - Luca Boccassi <[email protected]> + +- Import version 0.8.0 + This release adds support for v2.0.2+ MSFT broker versions and adds + JSON output for sso-mib-tool commands. Along that, many minor bugs + in the library have been fixed. + +------------------------------------------------------------------- Old: ---- sso-mib-0.7.0.tar.gz New: ---- sso-mib-0.8.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ sso-mib.spec ++++++ --- /var/tmp/diff_new_pack.06v7l8/_old 2026-03-18 16:51:49.500339577 +0100 +++ /var/tmp/diff_new_pack.06v7l8/_new 2026-03-18 16:51:49.504339743 +0100 @@ -18,7 +18,7 @@ %global soversion 0 Name: sso-mib -Version: 0.7.0 +Version: 0.8.0 Release: 1%{?dist} Summary: Tools and library for Single-Sign-On with CA for Entra via Himmelblau ++++++ sso-mib-0.7.0.tar.gz -> sso-mib-0.8.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/.github/workflows/build.yml new/sso-mib-0.8.0/.github/workflows/build.yml --- old/sso-mib-0.7.0/.github/workflows/build.yml 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/.github/workflows/build.yml 2026-03-17 10:52:48.000000000 +0100 @@ -15,6 +15,10 @@ permissions: contents: read +env: + LIBJWT3_VERSION: "3.2.3" + LIBJWT3_SHA256: c6d8a4ead0321317937cc29d8ebc5be48d114d02e007711bb2d4cca5d2a6d713 + jobs: reuse-and-codestyle: runs-on: ubuntu-24.04 @@ -84,9 +88,16 @@ fetch-depth: 0 - name: install dependencies run: | + if [[ "${{ matrix.distro }}" == "debian:bookworm" ]]; then + # Add backports repository for Debian bookworm + echo "deb http://deb.debian.org/debian bookworm-backports main" > /etc/apt/sources.list.d/backports.list + echo -e "Package: meson\nPin: release n=bookworm-backports\nPin-Priority: 501" | \ + tee /etc/apt/preferences.d/meson-backports.pref > /dev/null + fi apt-get update - apt-get install -y --no-install-recommends devscripts equivs + apt-get install -y --no-install-recommends devscripts equivs meson mk-build-deps --install -t "apt-get --no-install-recommends -y" + shell: bash - name: build package run: | dpkg-buildpackage -us -uc -b @@ -106,7 +117,41 @@ subject-path: | linux-amd64-deb/*.deb + build-with-libjwt3: + runs-on: ubuntu-24.04 + container: "debian:trixie" + steps: + - name: checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: install dependencies + run: | + apt-get update + apt-get install -y --no-install-recommends \ + build-essential meson cmake ninja-build curl ca-certificates \ + libjansson-dev libssl-dev \ + libgio-2.0-dev libjson-glib-dev libdbus-1-dev uuid-dev + - name: build libjwt3 + run: | + curl -L -q https://github.com/benmcollins/libjwt/releases/download/v${LIBJWT3_VERSION}/libjwt-${LIBJWT3_VERSION}.tar.xz \ + > /tmp/libjwt-${LIBJWT3_VERSION}.tar.xz + echo "$LIBJWT3_SHA256 /tmp/libjwt-${LIBJWT3_VERSION}.tar.xz" | sha256sum -c + WORKDIR=$(pwd) + cd /tmp + tar -xf libjwt-${LIBJWT3_VERSION}.tar.xz + cd libjwt-${LIBJWT3_VERSION} + cmake -B build -GNinja + cmake --build build + cmake --install build + cd $WORKDIR + - name: build sso-mib-tool + run: | + meson -Dlibjwt=enabled build + cd build && ninja sso-mib-tool + deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-24.04 needs: build permissions: @@ -117,6 +162,5 @@ url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy API docs - if: github.ref == 'refs/heads/main' id: deployment uses: actions/deploy-pages@v4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/README.md new/sso-mib-0.8.0/README.md --- old/sso-mib-0.7.0/README.md 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/README.md 2026-03-17 10:52:48.000000000 +0100 @@ -2,7 +2,8 @@ # Single-Sign-On using Microsoft Identity Broker (SSO-MIB) -This project implements a C library to interact with a locally running microsoft-identity-broker to get various authentication tokens via DBus. +sso-mib is a lightweight C library and CLI tool for interacting with a Microsoft Identity Broker to obtain authentication tokens via DBus. + By that, it implements support for the OIDC extension [MS-OAPXBC], sections [3.1.5.1.2 Request for Primary Refresh Token](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oapxbc/d32d5cd0-05d4-4ec2-8bcc-ac29ce711c23), [3.1.5.1.3 Exchange Primary Refresh Token for Access Token](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oapxbc/06e2bf0d-8cea-4b11-ad78-d212330ebda9) and can be used to obtain Proof-of-Possession tokens for RDP [[MS-RDPBCGR](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/e967ebeb-9e9f-443e-857a-5208802943c2)]. @@ -12,6 +13,7 @@ - JSON-Glib - libdbus - libuuid +- libjwt (only for sso-mib-tool) ## Interface @@ -32,8 +34,8 @@ ```c #include <sso-mib/sso-mib.h> -const gchar* client_id = "<my-client-uuid>"; -const gchar* authority = MIB_AUTHORITY_COMMON; +const gchar *client_id = "<my-client-uuid>"; +const gchar *authority = MIB_AUTHORITY_COMMON; MIBClientApp *app = mib_public_client_app_new(client_id, authority, NULL, NULL); GSList *scopes = NULL; @@ -49,8 +51,8 @@ MIBPrtSsoCookie *prt_cookie = mib_client_app_acquire_prt_sso_cookie(app, account, MIB_SSO_URL_DEFAULT, scopes); -const char * name = mib_prt_sso_cookie_get_name(cookie); -const char * value = mib_prt_sso_cookie_get_content(cookie); +const char *name = mib_prt_sso_cookie_get_name(cookie); +const char *value = mib_prt_sso_cookie_get_content(cookie); ``` Further examples are provided in `examples`. @@ -59,6 +61,16 @@ The `sso-mib-tool` provides a simple frontend to interact with the library. +For example, the following can be used to obtain a token for sending mail via SMTP: + +```bash +$ sso-mib-tool acquireTokenInteractive -f json \ + -s <client_id> \ + -r https://login.microsoftonline.com/common/oauth2/nativeclient \ + -x <authority> \ + -S offline_access -S 'https://outlook.office365.com/SMTP.Send' +``` + ## Maintainers - Felix Moessbauer <[email protected]> @@ -70,6 +82,10 @@ - `AF73F6EF5A53CFE304569F50E648A311F67A50FC` (Felix Moessbauer) +Since version `v0.8`, the following keys are used: + + 3785ED68D0F83B7BD7D23D7FE1136CEB2754A0BD (Felix Moessbauer) + ## License The library is licensed according to the terms of the GNU Lesser General Public License v2.1. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/debian/changelog new/sso-mib-0.8.0/debian/changelog --- old/sso-mib-0.7.0/debian/changelog 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/debian/changelog 2026-03-17 10:52:48.000000000 +0100 @@ -1,3 +1,9 @@ +sso-mib (0.8.0) unstable; urgency=medium + + * Update to 0.8.0 release + + -- Felix Moessbauer <[email protected]> Tue, 17 Mar 2026 10:45:07 +0100 + sso-mib (0.7.0) unstable; urgency=medium * add git credential helper for SMTP on O365 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/examples/avatar/main.c new/sso-mib-0.8.0/examples/avatar/main.c --- old/sso-mib-0.7.0/examples/avatar/main.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/examples/avatar/main.c 2026-03-17 10:52:48.000000000 +0100 @@ -53,6 +53,7 @@ { const gchar *client_id = EDGE_BROWSER_CLIENT_ID; const gchar *authority = MIB_AUTHORITY_COMMON; + int ret = 0; MIBClientApp *app = mib_public_client_app_new(client_id, authority, NULL, NULL); @@ -70,24 +71,29 @@ FILE *f = fopen("avatar.jpg", "w"); if (!f) { g_printerr("could not open file\n"); - g_slist_free_full(accounts, (GDestroyNotify)g_object_unref); - g_object_unref(app); - return -1; + ret = -1; + goto cleanup; } printf("Acquire Bearer token\n"); - scopes = g_slist_append(scopes, g_strdup(MIB_SCOPE_GRAPH_DEFAULT)); + scopes = g_slist_append(scopes, g_strdup("User.Read")); MIBPrt *prt = mib_client_app_acquire_token_silent(app, account, scopes, NULL, NULL, NULL); + if (!prt) { + printf("Failed to get Graph API token\n"); + ret = -1; + goto cleanup; + } + const char *token = mib_prt_get_access_token(prt); fetch_avatar(token, f); - fclose(f); printf("Successfully stored avatar picture in 'avatar.jpg'\n"); - /* cleanup */ +cleanup: + fclose(f); g_slist_free_full(scopes, (GDestroyNotify)g_free); g_slist_free_full(accounts, (GDestroyNotify)g_object_unref); - g_object_unref(prt); + g_clear_object(&prt); g_object_unref(app); - return 0; + return ret; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/examples/onedrive/main.c new/sso-mib-0.8.0/examples/onedrive/main.c --- old/sso-mib-0.7.0/examples/onedrive/main.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/examples/onedrive/main.c 2026-03-17 10:52:48.000000000 +0100 @@ -136,10 +136,7 @@ goto cleanup; mib_client_app_set_redirect_uri(app, APP_REDIRECT_URI); - scopes = g_slist_append(scopes, "Files.ReadWrite"); - scopes = g_slist_append(scopes, "Files.ReadWrite.All"); - scopes = g_slist_append(scopes, "Sites.ReadWrite.All"); - scopes = g_slist_append(scopes, "offline_access"); + scopes = g_slist_append(scopes, "Files.Read"); token = mib_client_app_acquire_token_interactive( app, scopes, MIB_PROMPT_UNSET, upn_hint, NULL, NULL, NULL); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/include/mib-prt.h new/sso-mib-0.8.0/include/mib-prt.h --- old/sso-mib-0.7.0/include/mib-prt.h 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/include/mib-prt.h 2026-03-17 10:52:48.000000000 +0100 @@ -12,7 +12,6 @@ #include <glib-object.h> #include "mib-exports.h" -#include "mib-prt.h" #include "mib-pop-params.h" /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/meson.build new/sso-mib-0.8.0/meson.build --- old/sso-mib-0.7.0/meson.build 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/meson.build 2026-03-17 10:52:48.000000000 +0100 @@ -3,10 +3,11 @@ project( 'sso-mib', 'c', - version : '0.7.0', - default_options : ['c_std=c11', 'warning_level=3'], + version : '0.8.0', + default_options : ['c_std=gnu11', 'warning_level=3'], ) project_description = 'Library to interact with the Microsoft Device Broker for SSO' +add_project_arguments('-Wno-unused-parameter', language : 'c') libsso_mib_hdrs = [ 'include/sso-mib.h', @@ -38,17 +39,26 @@ giodep = dependency('gio-2.0') jsondep = dependency('json-glib-1.0') uuiddep = dependency('uuid') -jwtdep = dependency('libjwt') +jwtdep = dependency('libjwt', required: get_option('libjwt')) + +summary({'libjwt (sso-mib-tool)': jwtdep.found()}, bool_yn: true, section: 'Misc dependencies') identity_broker = gnome.gdbus_codegen('identity-broker', sources: 'dbus/spec/com.microsoft.identity.broker1.xml', interface_prefix : 'com.microsoft.', namespace : 'mib_dbus', ) +identity_broker_obj = static_library( + 'identity_broker_obj', + identity_broker, + c_args : ['-Wno-pedantic'], + gnu_symbol_visibility : 'hidden', + dependencies : [giodep], + install : false) libsso_mib = shared_library( meson.project_name(), - libsso_mib_src, identity_broker, + libsso_mib_src, identity_broker[1], install : true, c_args : ['-DBUILDING_SSO_MIB=1', '-DSSO_MIB_COMPILATION=1', @@ -56,6 +66,7 @@ gnu_symbol_visibility : 'hidden', include_directories : public_headers, dependencies : [giodep, glibdep, jsondep, uuiddep], + link_with : [identity_broker_obj], version : meson.project_version(), soversion : '0', ) @@ -70,6 +81,16 @@ requires: ['glib-2.0', 'gio-2.0', 'uuid'] ) +sso_mib_tool_cargs_extra = [] +if jwtdep.found() + jwtdep_version = jwtdep.version().split('.') + sso_mib_tool_cargs_extra += [ + '-DWITH_LIBJWT', + '-DLIBJWT_VERSION_MAJOR=' + jwtdep_version[0], + '-DLIBJWT_VERSION_MINOR=' + jwtdep_version[1], + ] +endif + sso_mib_tool = executable( 'sso-mib-tool', [ @@ -78,6 +99,7 @@ install : true, include_directories : public_headers, link_with : [libsso_mib], + c_args: sso_mib_tool_cargs_extra, dependencies: [glibdep, giodep, jsondep, uuiddep, jwtdep], install_tag : 'tool' ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/meson_options.txt new/sso-mib-0.8.0/meson_options.txt --- old/sso-mib-0.7.0/meson_options.txt 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/meson_options.txt 2026-03-17 10:52:48.000000000 +0100 @@ -8,3 +8,7 @@ type: 'boolean', value: false, description: 'Build the examples [default=false]') +option('libjwt', + description: 'Enable code that depends on libjwt', + type: 'feature', + value: 'auto') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/mib-account.c new/sso-mib-0.8.0/src/mib-account.c --- old/sso-mib-0.7.0/src/mib-account.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/mib-account.c 2026-03-17 10:52:48.000000000 +0100 @@ -24,7 +24,7 @@ static void mib_account_finalize(GObject *gobject) { - MIBAccount *self = mib_account_get_instance_private(MIB_ACCOUNT(gobject)); + MIBAccount *self = MIB_ACCOUNT(gobject); g_clear_pointer(&self->client_info, g_free); g_clear_pointer(&self->environment, g_free); g_clear_pointer(&self->family_name, g_free); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/mib-client-app.c new/sso-mib-0.8.0/src/mib-client-app.c --- old/sso-mib-0.7.0/src/mib-client-app.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/mib-client-app.c 2026-03-17 10:52:48.000000000 +0100 @@ -6,7 +6,6 @@ #include "identity-broker.h" #include "mib-account-impl.h" #include "mib-client-app-impl.h" -#include "mib-account-impl.h" #include "mib-pop-params-impl.h" #include "mib-prt-impl.h" #include "mib-prt-sso-cookie-impl.h" @@ -16,12 +15,32 @@ #define DBUS_BROKER_PATH "/com/microsoft/identity/broker1" // according to https://msal-python.readthedocs.io/en/latest/#publicclientapplication -#define MIB_MS_BROKER_REDIRECT_URI_FMT \ - "ms-appx-web://Microsoft.AAD.BrokerPlugin/%s" +#define MIB_MS_BROKER_REDIRECT_URI \ + "https://login.microsoftonline.com/common/oauth2/nativeclient" // MSAL does not define any lower-bound (yet) #define MIB_REQUIRED_BROKER_PROTOCOL_VERSION "0.0" +/* + * Values from MSAL runtime (internal interface) + */ +enum AuthorizationType { + AT_NONE = 0, + AT_CACHED_REFRESH_TOKEN = 1, + AT_IMPORTED_REFRESH_TOKEN = 2, + AT_USERNAME_PASSWORD = 3, + AT_WINDOWS_INTEGRATED_AUTH = 4, + AT_AUTH_CODE = 5, + AT_INTERACTIVE = 6, + AT_CERTIFICATE = 7, + AT_PRT_SSO_COOKIE = 8, + AT_COMPLETE_BROKER_RESULT = 9, + AT_DEVICE_INFO_REQUEST = 10, + AT_SIGN_OUT_INTERACTIVE = 11, + AT_SIGN_OUT_SILENT = 12, + AT_ACCOUNT_TRANSFER = 13 +}; + struct _MIBClientApp { GObject parent_instance; @@ -39,13 +58,9 @@ static void mib_client_app_finalize(GObject *gobject) { - MIBClientApp *priv = - mib_client_app_get_instance_private(MIB_CLIENT_APP(gobject)); + MIBClientApp *priv = MIB_CLIENT_APP(gobject); g_clear_object(&priv->cancellable); g_clear_object(&priv->broker); - if (priv->cancellable) { - g_clear_object(&priv->cancellable); - } g_clear_pointer(&priv->authority, g_free); g_clear_pointer(&priv->redirect_uri, g_free); G_OBJECT_CLASS(mib_client_app_parent_class)->finalize(gobject); @@ -160,6 +175,10 @@ return accounts; } +/** + * If no upn is provided, return the first account. + * On error or when the no account has a matching upn return null. + */ static JsonObject *mib_client_app_get_account_by_upn_raw(MIBClientApp *app, const gchar *upn) { @@ -174,21 +193,24 @@ goto err; } for (guint i = 0; i < json_array_get_length(accounts_array); i++) { - account = json_array_get_object_element(accounts_array, i); + JsonObject *_account = json_array_get_object_element(accounts_array, i); if (!upn) { g_debug("no upn provided"); + account = _account; break; } - if (!json_object_has_member(account, "username")) - break; + if (!json_object_has_member(_account, "username")) + continue; const gchar *username = - json_object_get_string_member(account, "username"); + json_object_get_string_member(_account, "username"); if (g_strcmp0(username, upn) == 0) { g_debug("account matching UPN found"); + account = _account; break; } } - json_object_ref(account); + if (account) + json_object_ref(account); err: json_object_unref(accounts); return account; @@ -217,10 +239,10 @@ g_warning("error parsing account data"); break; } - accounts_list = g_slist_append(accounts_list, mib_account); + accounts_list = g_slist_prepend(accounts_list, mib_account); } json_object_unref(accounts); - return accounts_list; + return g_slist_reverse(accounts_list); } MIBAccount *mib_client_app_get_account_by_upn(MIBClientApp *app, @@ -267,7 +289,7 @@ gchar *mib_client_app_get_broker_redirect_uri(const MIBClientApp *self) { g_assert(self); - return g_strdup_printf(MIB_MS_BROKER_REDIRECT_URI_FMT, self->client_id); + return g_strdup(MIB_MS_BROKER_REDIRECT_URI); } void mib_client_app_set_redirect_uri(MIBClientApp *self, const gchar *uri) @@ -336,16 +358,18 @@ g_assert(app); g_assert(msal_cpp_version); - JsonObject *version_json; - gchar *version; + JsonObject *version_json = NULL; + gchar *version = NULL; version_json = mib_get_linux_broker_version_raw(app, msal_cpp_version); if (!version_json || !json_object_has_member(version_json, "linuxBrokerVersion")) { - return NULL; + goto err; } version = g_strdup( json_object_get_string_member(version_json, "linuxBrokerVersion")); - json_object_unref(version_json); +err: + if (version_json) + json_object_unref(version_json); return version; } @@ -353,20 +377,21 @@ prepare_prt_auth_params(MIBClientApp *app, JsonObject *account, JsonArray *scopes, const gchar *claims_challenge, JsonObject *auth_scheme, const gchar *renew_token, - JsonObject *extra_params) + JsonObject *extra_params, const gchar *sso_url, + enum AuthorizationType auth_type) { // { // 'accessTokenToRenew': renew_token, // 'account': account, // 'authority': context['authority'] - // 'authorizationType': 8, # OAUTH2 + // 'authorizationType': 8 (cookie with sso_url), 1 otherwise // 'clientId': client_id, // 'redirectUri': // '<context['authority']>/oauth2/nativeclient', // 'requestedScopes': ["https://graph.microsoft.com/.default"], // 'username': account['username'], + // 'ssoUrl': sso_url, // } - JsonNode *account_node = json_node_new(JSON_NODE_OBJECT); json_node_set_object(account_node, account); JsonNode *scopes_node = json_node_new(JSON_NODE_ARRAY); @@ -384,7 +409,7 @@ json_builder_set_member_name(builder, "authority"); json_builder_add_string_value(builder, mib_client_app_get_authority(app)); json_builder_set_member_name(builder, "authorizationType"); - json_builder_add_int_value(builder, 8); // OAUTH2 + json_builder_add_int_value(builder, auth_type); json_builder_set_member_name(builder, "clientId"); json_builder_add_string_value(builder, mib_client_app_get_client_id(app)); if (claims_challenge) { @@ -410,6 +435,10 @@ json_builder_add_value(builder, scopes_node); json_builder_set_member_name(builder, "username"); json_builder_add_string_value(builder, username); + if (sso_url) { + json_builder_set_member_name(builder, "ssoUrl"); + json_builder_add_string_value(builder, sso_url); + } json_builder_end_object(builder); JsonNode *root = json_builder_get_root(builder); @@ -430,15 +459,13 @@ gboolean ok; JsonObject *token; JsonObject *auth_params = prepare_prt_auth_params( - app, account, scopes, claims_challenge, auth_scheme, renew_token, NULL); + app, account, scopes, claims_challenge, auth_scheme, renew_token, NULL, + NULL, AT_CACHED_REFRESH_TOKEN); JsonNode *auth_params_node = json_node_new(JSON_NODE_OBJECT); json_node_set_object(auth_params_node, auth_params); json_object_unref(auth_params); JsonObject *params_obj = json_object_new(); - JsonNode *account_node = json_node_new(JSON_NODE_OBJECT); - json_node_set_object(account_node, account); - json_object_set_member(params_obj, "account", account_node); json_object_set_member(params_obj, "authParameters", auth_params_node); debug_print_json_object("mib_acquire_token_silent_raw", "request", params_obj); @@ -486,7 +513,7 @@ if (!token_json) { return NULL; } - MIBPrt *token = mib_prt_from_json(token_json); + MIBPrt *token = mib_prt_from_json(token_json, account); json_object_unref(token_json); return token; } @@ -502,9 +529,9 @@ gboolean ok; JsonObject *token; - JsonObject *auth_params = - prepare_prt_auth_params(app, account, scopes, claims_challenge, - auth_scheme, NULL, extra_params); + JsonObject *auth_params = prepare_prt_auth_params( + app, account, scopes, claims_challenge, auth_scheme, NULL, extra_params, + NULL, AT_INTERACTIVE); /* TODO: check if this is the correct key */ if (prompt != MIB_PROMPT_UNSET) { @@ -514,17 +541,15 @@ } JsonNode *auth_params_node = json_node_new(JSON_NODE_OBJECT); json_node_set_object(auth_params_node, auth_params); - json_object_unref(auth_params); JsonObject *params_obj = json_object_new(); /* if a re-auth is requested, clear the account */ if (prompt & MIB_PROMPT_SELECT_ACCOUNT) { json_object_remove_member(auth_params, "account"); json_object_remove_member(auth_params, "username"); - } else { - json_object_set_object_member(params_obj, "account", - json_object_ref(account)); } + json_object_unref(auth_params); + json_object_set_member(params_obj, "authParameters", auth_params_node); debug_print_json_object("mib_acquire_token_interactive_raw", "request", params_obj); @@ -568,6 +593,7 @@ if (!account_json) { return NULL; } + MIBAccount *fallback_account = mib_account_from_json(account_json); JsonArray *scopes_array = mib_scopes_to_json(scopes); JsonObject *pop_params = auth_scheme ? mib_pop_params_to_json(auth_scheme) : @@ -580,25 +606,24 @@ claims_challenge, pop_params, NULL); } if (token_json) { - token = mib_prt_from_json(token_json); - if (!token) - json_object_unref(token_json); + token = mib_prt_from_json(token_json, fallback_account); + json_object_unref(token_json); } if (!token) { token_json = mib_acquire_token_interactive_raw( app, scopes_array, prompt, account_json, domain_hint, claims_challenge, pop_params, NULL); - token = mib_prt_from_json(token_json); + if (token_json) { + token = mib_prt_from_json(token_json, fallback_account); + json_object_unref(token_json); + } } json_object_unref(account_json); json_array_unref(scopes_array); if (pop_params) { json_object_unref(pop_params); } - if (!token_json) { - return NULL; - } - json_object_unref(token_json); + g_clear_object(&fallback_account); return token; } @@ -637,7 +662,8 @@ gboolean ok; JsonObject *auth_params = - prepare_prt_auth_params(app, account, scopes, NULL, NULL, NULL, NULL); + prepare_prt_auth_params(app, account, scopes, NULL, NULL, NULL, NULL, + sso_url, AT_PRT_SSO_COOKIE); JsonObject *params = prepare_prt_sso_request_data(account, auth_params, sso_url); debug_print_json_object("mib_acquire_prt_sso_cookie_raw", "request", @@ -687,8 +713,8 @@ return cookie; } -static JsonObject *mib_generate_signed_http_request_raw( - MIBClientApp *app, const gchar *home_account_id, JsonObject *pop_params) +static JsonObject *mib_generate_signed_http_request_raw(MIBClientApp *app, + JsonObject *pop_params) { GError *error = NULL; gboolean ok; @@ -696,7 +722,6 @@ JsonObject *params = json_object_new(); json_object_set_string_member(params, "clientId", mib_client_app_get_client_id(app)); - json_object_set_string_member(pop_params, "homeAccountId", home_account_id); json_object_ref(pop_params); json_object_set_object_member(params, "popParams", pop_params); debug_print_json_object("mib_generate_signed_http_request_raw", "request", @@ -728,6 +753,7 @@ { JsonObject *params_json; gchar *access_token = NULL; + const gchar *account_id = NULL; g_assert(app); g_assert(account); @@ -737,8 +763,9 @@ } else { params_json = json_object_new(); } - JsonObject *token = mib_generate_signed_http_request_raw( - app, mib_account_get_home_account_id(account), params_json); + account_id = mib_account_get_home_account_id(account); + json_object_set_string_member(params_json, "homeAccountId", account_id); + JsonObject *token = mib_generate_signed_http_request_raw(app, params_json); json_object_unref(params_json); if (!token) { return NULL; @@ -782,7 +809,8 @@ JsonObject *resp_json = json_object_from_string(response); g_free(response); debug_print_json_object("mib_remove_account_raw", "response", resp_json); - json_object_unref(resp_json); + if (resp_json) + json_object_unref(resp_json); return 0; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/mib-pop-params.c new/sso-mib-0.8.0/src/mib-pop-params.c --- old/sso-mib-0.7.0/src/mib-pop-params.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/mib-pop-params.c 2026-03-17 10:52:48.000000000 +0100 @@ -19,8 +19,7 @@ static void mib_pop_params_finalize(GObject *gobject) { - MIBPopParams *priv = - mib_pop_params_get_instance_private(MIB_POP_PARAMS(gobject)); + MIBPopParams *priv = MIB_POP_PARAMS(gobject); g_clear_pointer(&priv->resource_req_uri, g_free); g_clear_pointer(&priv->shr_claims, g_free); g_clear_pointer(&priv->shr_nonce, g_free); @@ -95,6 +94,7 @@ { g_assert(self); g_assert(claims); + g_free(self->shr_claims); self->shr_claims = g_strdup(claims); } @@ -102,6 +102,7 @@ { g_assert(self); g_assert(nonce); + g_free(self->shr_nonce); self->shr_nonce = g_strdup(nonce); } @@ -109,5 +110,6 @@ { g_assert(self); g_assert(kid); + g_free(self->kid); self->kid = g_strdup(kid); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/mib-prt-impl.h new/sso-mib-0.8.0/src/mib-prt-impl.h --- old/sso-mib-0.7.0/src/mib-prt-impl.h 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/mib-prt-impl.h 2026-03-17 10:52:48.000000000 +0100 @@ -7,6 +7,6 @@ #include <json-glib/json-glib.h> #include "mib-prt.h" +#include "mib-account.h" -gchar *json_object_to_string(JsonObject *object); -MIBPrt *mib_prt_from_json(JsonObject *token_json); +MIBPrt *mib_prt_from_json(JsonObject *token_json, MIBAccount *fallback_account); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/mib-prt-sso-cookie.c new/sso-mib-0.8.0/src/mib-prt-sso-cookie.c --- old/sso-mib-0.7.0/src/mib-prt-sso-cookie.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/mib-prt-sso-cookie.c 2026-03-17 10:52:48.000000000 +0100 @@ -19,8 +19,7 @@ static void mib_prt_sso_cookie_finalize(GObject *gobject) { - MIBPrtSsoCookie *priv = - mib_prt_sso_cookie_get_instance_private(MIB_PRT_SSO_COOKIE(gobject)); + MIBPrtSsoCookie *priv = MIB_PRT_SSO_COOKIE(gobject); g_clear_pointer(&priv->name, g_free); g_clear_pointer(&priv->content, g_free); G_OBJECT_CLASS(mib_prt_sso_cookie_parent_class)->finalize(gobject); @@ -39,6 +38,18 @@ MIBPrtSsoCookie *mib_prt_sso_cookie_from_json(JsonObject *cookie_json) { MIBPrtSsoCookie *cookie; + /* microsoft-identity-broker > 2.0.1 */ + if (json_object_has_member(cookie_json, "cookieItems")) { + JsonArray *cookie_items = + json_object_get_array_member(cookie_json, "cookieItems"); + if (!cookie_items || !json_array_get_length(cookie_items)) { + return NULL; + } + JsonObject *cookie_json_inner = + json_array_get_object_element(cookie_items, 0); + return mib_prt_sso_cookie_from_json(cookie_json_inner); + } + if (!json_object_has_member(cookie_json, "cookieName") || !json_object_has_member(cookie_json, "cookieContent")) { g_warning("invalid cookie data"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/mib-prt.c new/sso-mib-0.8.0/src/mib-prt.c --- old/sso-mib-0.7.0/src/mib-prt.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/mib-prt.c 2026-03-17 10:52:48.000000000 +0100 @@ -24,7 +24,7 @@ static void mib_prt_finalize(GObject *gobject) { - MIBPrt *self = mib_prt_get_instance_private(MIB_PRT(gobject)); + MIBPrt *self = MIB_PRT(gobject); g_clear_pointer(&self->access_token, g_free); g_clear_object(&self->account); g_clear_pointer(&self->client_info, g_free); @@ -57,12 +57,11 @@ return MIB_AUTH_SCHEME_BEARER; } -MIBPrt *mib_prt_from_json(JsonObject *token_json) +MIBPrt *mib_prt_from_json(JsonObject *token_json, MIBAccount *fallback_account) { MIBAccount *account = NULL; - JsonObject *account_json = NULL; MIBPrt *token = NULL; - const char *members[] = { "accessToken", "accessTokenType", "account", + const char *members[] = { "accessToken", "accessTokenType", "clientInfo", "expiresOn", "idToken", "grantedScopes" }; @@ -82,8 +81,12 @@ g_strdup(json_object_get_string_member(broker_resp, "accessToken")); token->access_token_type = mib_prt_token_type_from_ext( json_object_get_int_member(broker_resp, "accessTokenType")); - account_json = json_object_get_object_member(broker_resp, "account"); - account = mib_account_from_json(account_json); + if (json_object_has_member(broker_resp, "account")) { + JsonObject *account_json = json_object_get_object_member(broker_resp, "account"); + account = mib_account_from_json(account_json); + } + if (!account && fallback_account) + account = g_object_ref(fallback_account); if (!account) { g_warning("account data is not valid"); g_object_unref(token); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/mib-utils.c new/sso-mib-0.8.0/src/mib-utils.c --- old/sso-mib-0.7.0/src/mib-utils.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/mib-utils.c 2026-03-17 10:52:48.000000000 +0100 @@ -31,9 +31,14 @@ g_object_unref(gen); } -void debug_print_json_object(gchar *func, gchar *scope, JsonObject *object) +void debug_print_json_object(const gchar *func, const gchar *scope, + JsonObject *object) { g_debug("json-object from %s,%s", func, scope); + if (!object) { + g_debug("(null)"); + return; + } print_json_object(object); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/mib-utils.h new/sso-mib-0.8.0/src/mib-utils.h --- old/sso-mib-0.7.0/src/mib-utils.h 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/mib-utils.h 2026-03-17 10:52:48.000000000 +0100 @@ -16,5 +16,6 @@ gchar *json_object_to_string(JsonObject *object); JsonObject *json_object_from_string(const gchar *data); -void debug_print_json_object(gchar *func, gchar *scope, JsonObject *object); +void debug_print_json_object(const gchar *func, const gchar *scope, + JsonObject *object); JsonArray *mib_scopes_to_json(GSList *scopes); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sso-mib-0.7.0/src/sso-mib-tool.c new/sso-mib-0.8.0/src/sso-mib-tool.c --- old/sso-mib-0.7.0/src/sso-mib-tool.c 2025-11-06 16:00:28.000000000 +0100 +++ new/sso-mib-0.8.0/src/sso-mib-tool.c 2026-03-17 10:52:48.000000000 +0100 @@ -4,11 +4,13 @@ */ #include <libgen.h> -#include <stdio.h> #include <signal.h> #include <sys/time.h> +#include <unistd.h> #include <json-glib/json-glib.h> +#ifdef WITH_LIBJWT #include <jwt.h> +#endif #include "sso-mib.h" @@ -17,36 +19,143 @@ // Fake MSAL CPP version #define MSAL_CPP_VERSION "1.28.0" +#define FORMAT_JSON "json" +#define FORMAT_TEXT "text" + static GCancellable *cancellable = NULL; static void sig_handler(int signo) { if (signo == SIGINT) { if (cancellable) { - g_print("Interrupted. Cancel in-flight operations.\n"); + const char msg[] = "Interrupted. Cancel in-flight operations.\n"; + // use a dummy conditional to denote we don't care about the result + if (write(STDERR_FILENO, msg, sizeof(msg) - 1)) { + } g_cancellable_cancel(cancellable); } } } -static void print_decoded_jwt(const gchar *token) +#ifdef WITH_LIBJWT +#if LIBJWT_VERSION_MAJOR == 1 || LIBJWT_VERSION_MAJOR == 2 +static int decode_headers_and_claims(const gchar *token, char **grants, + char **hdrs) { jwt_t *jwt = NULL; - int ret = 0; + int ret = jwt_decode(&jwt, token, NULL, 0); + if (ret != 0) + return ret; + *hdrs = jwt_get_headers_json(jwt, NULL); + *grants = jwt_get_grants_json(jwt, NULL); + jwt_free(jwt); + return 0; +} +#else +struct jwt_cb_ctx { + char **grants; + char **hdrs; +}; + +static int on_check_cb(jwt_t *jwt, jwt_config_t *config) +{ + jwt_value_t header_val; + jwt_value_t claims_val; + struct jwt_cb_ctx *ctx = config->ctx; + + jwt_set_GET_JSON(&header_val, NULL); + if (jwt_header_get(jwt, &header_val) == JWT_VALUE_ERR_NONE) { + *ctx->hdrs = header_val.json_val; + } + jwt_set_GET_JSON(&claims_val, NULL); + if (jwt_claim_get(jwt, &claims_val) == JWT_VALUE_ERR_NONE) + *ctx->grants = claims_val.json_val; + return 0; +} + +static int decode_headers_and_claims(const gchar *token, char **grants, + char **hdrs) +{ + struct jwt_cb_ctx ctx = { .grants = grants, .hdrs = hdrs }; + jwt_checker_t *checker = jwt_checker_new(); + jwt_checker_setcb(checker, on_check_cb, &ctx); + jwt_checker_verify(checker, token); + jwt_checker_free(checker); + return 0; +} +#endif + +static void print_decoded_jwt(const gchar *token) +{ char *grants = NULL; char *hdrs = NULL; - ret = jwt_decode(&jwt, token, NULL, 0); - if (ret != 0) { + if (decode_headers_and_claims(token, &grants, &hdrs) != 0) { g_print("Error: Failed to decode JWT\n"); return; } - hdrs = jwt_get_headers_json(jwt, NULL); - grants = jwt_get_grants_json(jwt, NULL); g_print("%s\n", hdrs); g_print("%s\n", grants); free(hdrs); free(grants); - jwt_free(jwt); +} +#else +static void print_decoded_jwt(const gchar *token) +{ + g_printerr("token decoding requires libjwt (dependency missing)\n"); +} +#endif + +static void print_json_builder(JsonBuilder *builder) +{ + JsonGenerator *generator = json_generator_new(); + JsonNode *root = json_builder_get_root(builder); + json_generator_set_root(generator, root); + + gchar *buf = json_generator_to_data(generator, NULL); + g_print("%s\n", buf); + g_free(buf); + + json_node_free(root); + g_object_unref(generator); +} + +static void json_builder_add_jwt_token(JsonBuilder *builder, const gchar *token) +{ + char *grants = NULL; + char *hdrs = NULL; + if (decode_headers_and_claims(token, &grants, &hdrs) != 0) { + g_print("Error: Failed to decode JWT\n"); + return; + } + + json_builder_begin_object(builder); + + GError *error = NULL; + JsonParser *parser = json_parser_new(); + if (json_parser_load_from_data(parser, hdrs, -1, &error)) { + JsonNode *node = json_parser_get_root(parser); + json_builder_set_member_name(builder, "headers"); + JsonNode *copied = json_node_copy(node); + json_builder_add_value(builder, copied); + } else { + g_printerr("Error parsing JSON string: %s\n", error->message); + } + g_clear_error(&error); + + if (json_parser_load_from_data(parser, grants, -1, &error)) { + JsonNode *node = json_parser_get_root(parser); + json_builder_set_member_name(builder, "grants"); + JsonNode *copied = json_node_copy(node); + json_builder_add_value(builder, copied); + } else { + g_printerr("Error parsing JSON string: %s\n", error->message); + } + g_clear_error(&error); + + g_object_unref(parser); + free(hdrs); + free(grants); + json_builder_end_object(builder); } static void print_prt_sso_cookie(MIBPrtSsoCookie *cookie, int decode) @@ -63,6 +172,27 @@ } } +static void json_print_prt_sso_cookie(MIBPrtSsoCookie *cookie, int decode) +{ + JsonBuilder *builder = json_builder_new(); + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "cookie_name"); + json_builder_add_string_value(builder, mib_prt_sso_cookie_get_name(cookie)); + + json_builder_set_member_name(builder, "cookie_content"); + const gchar *content = mib_prt_sso_cookie_get_content(cookie); + if (decode) { + json_builder_add_jwt_token(builder, content); + } else { + json_builder_add_string_value(builder, content); + } + + json_builder_end_object(builder); + print_json_builder(builder); + g_object_unref(builder); +} + static void print_account(MIBAccount *account, gchar *prefix) { char realm_str[37]; @@ -87,6 +217,90 @@ g_print("%susername: %s\n", prefix, mib_account_get_username(account)); } +static void print_account_list(GSList *accounts, gchar *prefix) +{ + int i = 0; + for (GSList *iter = accounts; iter; iter = g_slist_next(iter)) { + g_print("# Account %d\n", i++); + MIBAccount *account = (MIBAccount *)iter->data; + print_account(account, " "); + } +} + +static void json_builder_add_account(JsonBuilder *builder, MIBAccount *account) +{ + char realm_str[37]; + uuid_t realm; + mib_account_get_realm(account, realm); + uuid_unparse(realm, realm_str); + + if (mib_account_get_client_info(account)) { + json_builder_set_member_name(builder, "client_info"); + json_builder_add_string_value(builder, + mib_account_get_client_info(account)); + } + + json_builder_set_member_name(builder, "environment"); + json_builder_add_string_value(builder, + mib_account_get_environment(account)); + + if (mib_account_get_family_name(account)) { + json_builder_set_member_name(builder, "family_name"); + json_builder_add_string_value(builder, + mib_account_get_family_name(account)); + } + + json_builder_set_member_name(builder, "given_name"); + json_builder_add_string_value(builder, mib_account_get_given_name(account)); + + json_builder_set_member_name(builder, "home_account_id"); + json_builder_add_string_value(builder, + mib_account_get_home_account_id(account)); + + json_builder_set_member_name(builder, "local_account_id"); + json_builder_add_string_value(builder, + mib_account_get_local_account_id(account)); + + json_builder_set_member_name(builder, "name"); + json_builder_add_string_value(builder, mib_account_get_name(account)); + + json_builder_set_member_name(builder, "password_expiry"); + json_builder_add_int_value(builder, + mib_account_get_password_expiry(account)); + + json_builder_set_member_name(builder, "realm"); + json_builder_add_string_value(builder, realm_str); + + json_builder_set_member_name(builder, "username"); + json_builder_add_string_value(builder, mib_account_get_username(account)); +} + +static void json_print_account(MIBAccount *account) +{ + JsonBuilder *builder = json_builder_new(); + json_builder_begin_object(builder); + json_builder_add_account(builder, account); + json_builder_end_object(builder); + print_json_builder(builder); + g_object_unref(builder); +} + +static void json_print_account_list(GSList *accounts) +{ + JsonBuilder *builder = json_builder_new(); + json_builder_begin_array(builder); + + for (GSList *iter = accounts; iter; iter = g_slist_next(iter)) { + json_builder_begin_object(builder); + json_builder_add_account(builder, (MIBAccount *)iter->data); + json_builder_end_object(builder); + } + + json_builder_end_array(builder); + print_json_builder(builder); + g_object_unref(builder); +} + static const char *auth_scheme_to_str(enum MIB_AUTH_SCHEME scheme) { if (scheme == MIB_AUTH_SCHEME_POP) { @@ -99,7 +313,7 @@ static void print_prt_token(MIBPrt *token, int decode) { char buffer[32]; - struct tm *tm_info; + struct tm tm_info; struct timeval tv; const char *token_type = auth_scheme_to_str(mib_prt_get_access_token_type(token)); @@ -123,13 +337,65 @@ } g_print("\n"); tv.tv_sec = mib_prt_get_expires_on(token); - tm_info = localtime(&tv.tv_sec); - strftime(buffer, 32, "%Y-%m-%d %H:%M:%S", tm_info); + localtime_r(&tv.tv_sec, &tm_info); + strftime(buffer, sizeof(buffer) - 1, "%Y-%m-%d %H:%M:%S", &tm_info); g_print("%sexpires-on: %s\n", p, buffer); g_print("%saccount:\n", p); print_account(mib_prt_get_account(token), "# "); } +static void json_print_prt_token(MIBPrt *token, int decode) +{ + JsonBuilder *builder = json_builder_new(); + json_builder_begin_object(builder); + if (decode) { + json_builder_set_member_name(builder, "access_token"); + json_builder_add_jwt_token(builder, mib_prt_get_access_token(token)); + json_builder_set_member_name(builder, "id_token"); + json_builder_add_jwt_token(builder, mib_prt_get_id_token(token)); + } else { + json_builder_set_member_name(builder, "access_token"); + json_builder_add_string_value(builder, mib_prt_get_access_token(token)); + json_builder_set_member_name(builder, "id_token"); + json_builder_add_string_value(builder, mib_prt_get_id_token(token)); + } + + json_builder_set_member_name(builder, "access_token_type"); + json_builder_add_string_value( + builder, auth_scheme_to_str(mib_prt_get_access_token_type(token))); + + json_builder_set_member_name(builder, "client_info"); + json_builder_add_string_value(builder, mib_prt_get_client_info(token)); + + json_builder_set_member_name(builder, "granted_scopes"); + gchar *const *scopes = mib_prt_get_granted_scopes(token); + json_builder_begin_array(builder); + for (int i = 0; scopes[i]; i++) { + json_builder_add_string_value(builder, scopes[i]); + } + json_builder_end_array(builder); + + struct timeval tv; + tv.tv_sec = mib_prt_get_expires_on(token); + struct tm tm_info; + localtime_r(&tv.tv_sec, &tm_info); + char buffer[32]; + strftime(buffer, sizeof(buffer) - 1, "%Y-%m-%d %H:%M:%S", &tm_info); + json_builder_set_member_name(builder, "expires_on"); + json_builder_add_string_value(builder, buffer); + + json_builder_set_member_name(builder, "account"); + json_builder_begin_object(builder); + json_builder_add_account(builder, mib_prt_get_account(token)); + json_builder_end_object(builder); + + // finish builder + json_builder_end_object(builder); + + print_json_builder(builder); + g_object_unref(builder); +} + static JsonObject *parse_to_json_object(const gchar *data) { JsonParser *parser = json_parser_new(); @@ -185,7 +451,7 @@ json_object_get_string_member(params_json, "resourceRequestUri"); params = mib_pop_params_new(auth_scheme, req_method, req_uri); if (!params) { - return NULL; + goto err; } if (json_object_has_member(params_json, "shrClaims")) { mib_pop_params_set_shr_claims( @@ -193,15 +459,23 @@ } if (json_object_has_member(params_json, "shrNonce")) { mib_pop_params_set_shr_nonce( - params, - g_strdup(json_object_get_string_member(params_json, "shrNonce"))); + params, json_object_get_string_member(params_json, "shrNonce")); } return params; err: - g_object_unref(params); + if (params) + g_object_unref(params); return NULL; } +GSList *default_scope_if_empty(GSList *scopes) +{ + if (!scopes) { + scopes = g_slist_append(scopes, g_strdup(MIB_SCOPE_GRAPH_DEFAULT)); + } + return scopes; +} + static void print_help(char *name) { g_print("Usage: %s COMMAND [OPTION]...\n", basename(name)); @@ -213,26 +487,36 @@ g_print(" -a <account> Account index (default: 0)\n"); g_print(" -A <upn> Select account by User Principal Name\n"); g_print(" -d Decode JWT\n"); + g_print(" -f <format> Set output format: %s, %s (default: %s)\n", + FORMAT_TEXT, FORMAT_JSON, FORMAT_TEXT); g_print(" -h Print this help message\n"); g_print(" -I Enforce interactive token acquire\n"); g_print(" -P Proof-of-Possession parameters\n"); + g_print(" -r <uri> OIDC redirect URI\n"); g_print(" -s <client_id> Azure client application ID (default: %s)\n", CLIENT_ID_DEFAULT); + g_print(" -S <scope> OIDC scope (repeatable)\n"); g_print(" -t <token> Renew token\n"); + g_print(" -x <authority> Entra ID authority (default: %s)\n", + MIB_AUTHORITY_COMMON); } int main(int argc, char **argv) { int account_idx = 0; char *account_hint = NULL; + const gchar *authority = MIB_AUTHORITY_COMMON; char *command = NULL; gchar *client_id = CLIENT_ID_DEFAULT; gchar *pop_params = NULL; JsonObject *pop_params_json = NULL; MIBPopParams *auth_params = NULL; + gchar *redirect_uri = NULL; + GSList *scopes = NULL; char *renew_token = NULL; int decode = 0; int enforce_interactive = 0; + gchar *format = FORMAT_TEXT; int c; if (argc < 2) { print_help(argv[0]); @@ -243,7 +527,7 @@ print_help(argv[0]); return 0; } - while ((c = getopt(argc - 1, argv + 1, "a:A:dhIP:s:t:")) != -1) + while ((c = getopt(argc - 1, argv + 1, "a:A:df:hIP:r:s:S:t:x:")) != -1) switch (c) { case 'a': account_idx = atoi(optarg); @@ -255,6 +539,9 @@ case 'd': decode = 1; break; + case 'f': + format = optarg; + break; case 'h': print_help(argv[0]); return 0; @@ -264,12 +551,22 @@ case 'P': pop_params = optarg; break; + case 'r': + g_clear_pointer(&redirect_uri, g_free); + redirect_uri = g_strdup(optarg); + break; case 's': client_id = optarg; break; + case 'S': + scopes = g_slist_append(scopes, g_strdup(optarg)); + break; case 't': renew_token = optarg; break; + case 'x': + authority = optarg; + break; case '?': print_help(argv[0]); return 1; @@ -280,8 +577,12 @@ g_print("Error: -c <command> is required\n"); return 1; } + if (scopes && (strncmp(command, "acquire", strlen("acquire")) != 0)) { + g_slist_free_full(scopes, g_free); + g_printerr( + "Warning: scopes must only be provided on acquire* commands. Ignoring\n"); + } - const gchar *authority = MIB_AUTHORITY_COMMON; cancellable = g_cancellable_new(); MIBClientApp *app = mib_public_client_app_new(client_id, authority, cancellable, NULL); @@ -290,6 +591,10 @@ return 1; } mib_client_app_set_enforce_interactive(app, enforce_interactive); + if (redirect_uri) { + mib_client_app_set_redirect_uri(app, redirect_uri); + g_clear_pointer(&redirect_uri, g_free); + } // register cancellation handler signal(SIGINT, sig_handler); @@ -316,7 +621,14 @@ g_object_unref(cancellable); return 1; } - print_account(account, " "); + if (g_ascii_strcasecmp(format, FORMAT_TEXT) == 0) { + print_account(account, " "); + } else if (g_ascii_strcasecmp(format, FORMAT_JSON) == 0) { + json_print_account(account); + } else { + g_print("Error[getAccounts]: Unsupported output format: %s\n", + format); + } g_object_unref(account); } else if (strcmp(command, "getAccounts") == 0) { GSList *accounts = mib_client_app_get_accounts(app); @@ -326,10 +638,13 @@ g_object_unref(cancellable); return 1; } - for (GSList *iter = accounts; iter; iter = g_slist_next(iter)) { - g_print("# Account %d\n", account_idx++); - MIBAccount *account = (MIBAccount *)iter->data; - print_account(account, " "); + if (g_ascii_strcasecmp(format, FORMAT_TEXT) == 0) { + print_account_list(accounts, " "); + } else if (g_ascii_strcasecmp(format, FORMAT_JSON) == 0) { + json_print_account_list(accounts); + } else { + g_print("Error[getAccounts]: Unsupported output format: %s\n", + format); } g_slist_free_full(accounts, (GDestroyNotify)g_object_unref); } else if (strcmp(command, "removeAccount") == 0) { @@ -353,8 +668,7 @@ return 1; } } else if (strcmp(command, "acquirePrtSsoCookie") == 0) { - GSList *scopes = NULL; - scopes = g_slist_append(scopes, g_strdup(MIB_SCOPE_GRAPH_DEFAULT)); + scopes = default_scope_if_empty(scopes); GSList *accounts = mib_client_app_get_accounts(app); if (!accounts) { g_print("Error[acquirePrtSsoCookie]: No accounts found\n"); @@ -375,11 +689,18 @@ g_object_unref(cancellable); return 1; } - print_prt_sso_cookie(prt_cookie, decode); + if (g_ascii_strcasecmp(format, FORMAT_TEXT) == 0) { + print_prt_sso_cookie(prt_cookie, decode); + } else if (g_ascii_strcasecmp(format, FORMAT_JSON) == 0) { + json_print_prt_sso_cookie(prt_cookie, decode); + } else { + g_print( + "Error[acquirePrtSsoCookie]: Unsupported output format: %s\n", + format); + } g_object_unref(prt_cookie); } else if (strcmp(command, "acquireTokenSilent") == 0) { - GSList *scopes = NULL; - scopes = g_slist_append(scopes, g_strdup(MIB_SCOPE_GRAPH_DEFAULT)); + scopes = default_scope_if_empty(scopes); GSList *accounts = mib_client_app_get_accounts(app); if (!accounts) { g_print("Error[acquireTokenSilent]: No accounts found\n"); @@ -401,11 +722,18 @@ g_object_unref(cancellable); return 1; } - print_prt_token(prt_token, decode); + if (g_ascii_strcasecmp(format, FORMAT_TEXT) == 0) { + print_prt_token(prt_token, decode); + } else if (g_ascii_strcasecmp(format, FORMAT_JSON) == 0) { + json_print_prt_token(prt_token, decode); + } else { + g_print( + "Error[acquireTokenSilent]: Unsupported output format: %s\n", + format); + } g_object_unref(prt_token); } else if (strcmp(command, "acquireTokenInteractive") == 0) { - GSList *scopes = NULL; - scopes = g_slist_append(scopes, g_strdup(MIB_SCOPE_GRAPH_DEFAULT)); + scopes = default_scope_if_empty(scopes); MIBPrt *prt_token = mib_client_app_acquire_token_interactive( app, scopes, MIB_PROMPT_CONSENT, NULL, NULL, NULL, auth_params); g_slist_free_full(scopes, g_free); @@ -418,7 +746,15 @@ g_object_unref(cancellable); return 1; } - print_prt_token(prt_token, decode); + if (g_ascii_strcasecmp(format, FORMAT_TEXT) == 0) { + print_prt_token(prt_token, decode); + } else if (g_ascii_strcasecmp(format, FORMAT_JSON) == 0) { + json_print_prt_token(prt_token, decode); + } else { + g_print( + "Error[acquireTokenInteractive]: Unsupported output format: %s\n", + format); + } g_object_unref(prt_token); } else if (strcmp(command, "getLinuxBrokerVersion") == 0) { gchar *version = @@ -456,10 +792,28 @@ g_object_unref(auth_params); g_slist_free_full(accounts, (GDestroyNotify)g_object_unref); if (token) { - if (decode) { - print_decoded_jwt(token); + if (g_ascii_strcasecmp(format, FORMAT_TEXT) == 0) { + if (decode) { + print_decoded_jwt(token); + } else { + g_print("HTTP request token: %s\n", token); + } + } else if (g_ascii_strcasecmp(format, FORMAT_JSON) == 0) { + JsonBuilder *builder = json_builder_new(); + json_builder_begin_object(builder); + json_builder_set_member_name(builder, "token"); + if (decode) { + json_builder_add_jwt_token(builder, token); + } else { + json_builder_add_string_value(builder, token); + } + json_builder_end_object(builder); + print_json_builder(builder); + g_object_unref(builder); } else { - g_print("HTTP request token: %s\n", token); + g_print( + "Error[generateSignedHttpRequest]: Unsupported output format: %s\n", + format); } g_free(token); } else {
