Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 113 additions & 26 deletions cups/oauth.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ typedef enum _cups_otype_e // OAuth data type
#define _CUPS_OAUTH_REDIRECT_PATHLEN 2
// Redirect URI request path length

#define _CUPS_OAUTH_USERID_TIMEOUT 300
// Default user ID cache lifetime in seconds

#ifdef DEBUG
static const char * const cups_otypes[_CUPS_OTYPE_MAX] =
{ // OAuth data types...
Expand Down Expand Up @@ -169,6 +172,7 @@ static char *oauth_make_access_uri(const char *access_token);
static char *oauth_make_path(char *buffer, size_t bufsize, const char *auth_uri, const char *secondary_uri, _cups_otype_t otype, bool use_sysconfig);
static char *oauth_make_software_id(char *buffer, size_t bufsize);
static bool oauth_metadata_contains(cups_json_t *metadata, const char *parameter, const char *value);
static void oauth_save_user_id(const char *auth_uri, const char *access_uri, const char *user_id, time_t expires);
static void oauth_save_value(const char *auth_uri, const char *secondary_uri, _cups_otype_t otype, const char *value);
static bool oauth_set_error(cups_json_t *json, size_t num_form, cups_option_t *form);

Expand Down Expand Up @@ -1474,8 +1478,11 @@ cupsOAuthGetTokens(
// 'cupsOAuthGetUserId()' - Get the user ID token associated with the given access token.
//
// This function retrieves the user ID token associated with a given access
// token. The user ID information is cached until the token expires to minimize
// the overhead of communicating with the Authorization Server.
// token. The user ID information is cached until the access token expires, as
// saved by @link cupsOAuthSaveTokens@, or for five minutes after it is
// retrieved from the Authorization Server's "userinfo_endpoint", to minimize
// the overhead of communicating with the Authorization Server. Only user
// information containing a subject ("sub") is returned or cached.
//

cups_jwt_t * // O - Identification information or `NULL` if none
Expand All @@ -1485,9 +1492,11 @@ cupsOAuthGetUserId(
const char *access_token) // I - Access (Bearer) token
{
char *access_uri, // Access token URI
*user_id_value; // String value of user ID
*user_id_value, // String value of user ID
*expptr; // Pointer to expiration time
cups_jwt_t *user_id = NULL; // User ID JWT
const char *userinfo_ep; // userinfo_endpoint value
http_t *http = NULL; // Connection to endpoint


// Range check input...
Expand All @@ -1499,10 +1508,49 @@ cupsOAuthGetUserId(
return (NULL);

// See if we have this token cached already...
if ((user_id_value = oauth_load_value(auth_uri, access_uri, _CUPS_OTYPE_USER_ID, /*try_sysconfig*/false)) == NULL && (userinfo_ep = cupsJSONGetString(cupsJSONFind(metadata, "userinfo_endpoint"))) != NULL)
if ((user_id_value = oauth_load_value(auth_uri, access_uri, _CUPS_OTYPE_USER_ID, /*try_sysconfig*/false)) != NULL)
{
// Yes, use it if it hasn't expired...
if ((expptr = strchr(user_id_value, '\n')) != NULL && strtol(expptr + 1, NULL, 10) > time(NULL))
{
*expptr = '\0';

if (*user_id_value == '{')
{
// Claims from the userinfo endpoint...
cups_json_t *claims = cupsJSONImportString(user_id_value);
// Cached claims

if (claims && (user_id = cupsJWTNew(/*type*/NULL, claims)) == NULL)
cupsJSONDelete(claims);
}
else
{
// ID token from cupsOAuthSaveTokens...
user_id = cupsJWTImportString(user_id_value, CUPS_JWS_FORMAT_COMPACT);
}

if (user_id && !cupsJWTGetClaimString(user_id, CUPS_JWT_SUB))
{
// No subject...
cupsJWTDelete(user_id);
user_id = NULL;
}
}

free(user_id_value);
user_id_value = NULL;

if (user_id)
goto done;

// Remove the expired or unusable user ID...
oauth_save_value(auth_uri, access_uri, _CUPS_OTYPE_USER_ID, /*value*/NULL);
}

if ((userinfo_ep = cupsJSONGetString(cupsJSONFind(metadata, "userinfo_endpoint"))) != NULL)
{
// No, try to get the information from the Authorization Server...
http_t *http; // Connection to endpoint
char host[256], // Endpoint hostname
resource[1024]; // Endpoint resource
int port; // Endpoint port
Expand All @@ -1513,7 +1561,7 @@ cupsOAuthGetUserId(

// Connect to the endpoint...
if ((http = httpConnectURI(userinfo_ep, host, sizeof(host), &port, resource, sizeof(resource), /*blocking*/true, /*msec*/30000, /*cancel*/NULL, /*require_ca*/true)) == NULL)
return (NULL);
goto done;

// Send a GET request with the access token...
httpSetAuthString(http, "Bearer", access_token);
Expand All @@ -1539,41 +1587,48 @@ cupsOAuthGetUserId(
user_id_claims = cupsJSONImportString(user_id_value);

// Verify that the access token hasn't expired...
if (cupsJSONFind(user_id_claims, "error"))
if (status != HTTP_STATUS_OK || cupsJSONFind(user_id_claims, "error"))
{
// Expired or bad access token, get rid of this one...
// Expired or bad access token...
const char *error_desc = cupsJSONGetString(cupsJSONFind(user_id_claims, "error_description"));
// Description of error

DEBUG_printf("1cupsOAuthGetUserId: Error from userinfo - %s, %s.", cupsJSONGetString(cupsJSONFind(user_id_claims, "error")), error_desc);
DEBUG_printf("1cupsOAuthGetUserId: Error from userinfo - %d, %s, %s.", status, cupsJSONGetString(cupsJSONFind(user_id_claims, "error")), error_desc);

if (!error_desc && status != HTTP_STATUS_OK)
error_desc = httpStatusString(status);

_cupsSetError(IPP_STATUS_ERROR_INTERNAL, error_desc, false);

cupsJSONDelete(user_id_claims);
oauth_save_value(auth_uri, access_uri, _CUPS_OTYPE_USER_ID, /*value*/NULL);
}
else if (!cupsJSONGetString(cupsJSONFind(user_id_claims, "sub")))
{
// No subject in the user information...
_cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Missing subject in user information."), true);

cupsJSONDelete(user_id_claims);
}
else if ((user_id = cupsJWTNew(/*type*/NULL, user_id_claims)) != NULL)
{
// Created a new JWT with the JSON user information, save it for future use...
// Created a new JWT with the JSON user information, save the claims for
// future use...
free(user_id_value);

user_id_value = cupsJWTExportString(user_id, CUPS_JWS_FORMAT_COMPACT);
oauth_save_value(auth_uri, access_uri, _CUPS_OTYPE_USER_ID, user_id_value);
user_id_value = cupsJSONExportString(user_id_claims);
oauth_save_user_id(auth_uri, access_uri, user_id_value, /*expires*/0);
}
else
{
// Free the JSON information since we couldn't create the JWT...
cupsJSONDelete(user_id_claims);
}
}
else if (user_id_value)
{
// Convert the string to a JWT...
user_id = cupsJWTImportString(user_id_value, CUPS_JWS_FORMAT_COMPACT);
}

done:

// Free strings and return...
httpClose(http);
free(access_uri);
free(user_id_value);

Expand Down Expand Up @@ -1800,7 +1855,7 @@ cupsOAuthSaveTokens(

if ((access_uri = oauth_make_access_uri(access_token)) != NULL)
{
oauth_save_value(auth_uri, access_uri, _CUPS_OTYPE_USER_ID, user_id);
oauth_save_user_id(auth_uri, access_uri, user_id, access_expires);
free(access_uri);
}
}
Expand Down Expand Up @@ -2231,6 +2286,9 @@ oauth_make_path(

if (secondary_uri)
{
const char *hashdata = secondary_temp;
// Data to hash

if (!strncmp(secondary_uri, "http://", 7))
{
// HTTP URI
Expand All @@ -2251,15 +2309,12 @@ oauth_make_path(
}
else
{
// IPPS URI
cupsCopyString(secondary_temp, secondary_uri + 7, sizeof(secondary_temp));
if ((ptr = strchr(secondary_temp, '/')) != NULL)
*ptr = '\0'; // Strip resource path
if (!strchr(secondary_temp, ':')) // Add :631 if no port is present
cupsConcatString(secondary_temp, ":631", sizeof(secondary_temp));
// Access token URN - hash the whole token since it can contain '/' and
// be longer than secondary_temp...
hashdata = secondary_uri;
}

cupsHashData("sha2-256", secondary_temp, strlen(secondary_temp), secondary_hash, sizeof(secondary_hash));
cupsHashData("sha2-256", hashdata, strlen(hashdata), secondary_hash, sizeof(secondary_hash));
cupsHashString(secondary_hash, sizeof(secondary_hash), secondary_temp, sizeof(secondary_temp));
}
else
Expand Down Expand Up @@ -2372,6 +2427,38 @@ oauth_metadata_contains(
}


//
// 'oauth_save_user_id()' - Save the cached user ID for an access token.
//

static void
oauth_save_user_id(
const char *auth_uri, // I - Authorization Server URI
const char *access_uri, // I - Access token URI
const char *user_id, // I - User ID or `NULL` to delete
time_t expires) // I - Expiration time or `0` for default
{
char *value = NULL; // User ID and expiration time
size_t valuesize; // Size of value


if (user_id)
{
// Save the user ID followed by its expiration time, like the access token...
if (expires <= 0)
expires = time(NULL) + _CUPS_OAUTH_USERID_TIMEOUT;

valuesize = strlen(user_id) + 24;
if ((value = malloc(valuesize)) != NULL)
snprintf(value, valuesize, "%s\n%ld\n", user_id, (long)expires);
}

oauth_save_value(auth_uri, access_uri, _CUPS_OTYPE_USER_ID, value);

free(value);
}


//
// 'oauth_save_value()' - Save a value string to the OAuth store.
//
Expand Down