diff --git a/README.md b/README.md index d5a862f..aa1ee83 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,20 @@ $ sudo make install ## Configuration ``` -auth sufficient pam_oauth2.so key1=value2 key2=value2 +auth sufficient pam_oauth2.so [POST] [:[:]] key1=value2 key2=value2 account sufficient pam_oauth2.so ``` +Optional parameter `POST` indicates to use POST method when sending request to the ``. This also adds +`Content-Type: application/x-www-form-urlencoded` header and moves CGI parameters from `` into request body. +If this parameter is omitted, the GET method is used. + +Optional parameter `:[:]` specify a field value for `Authorization` HTTP header to be sent along with ``. If authorisation scheme `` is omitted, the Basic authorisation scheme is used. Examples: +- `:dXNlcjpwYXNzd29yZA==` will result in `Authorization: Basic dXNlcjpwYXNzd29yZA==` header to be added; +- `:Bearer:mF_9.B5f-4.1JqM` will result in `Authorization: Bearer mF_9.B5f-4.1JqM` header to be added. + +If this parameter is omitted, no `Authorization` header is added. + ## How it works Lets assume that configuration is looking like: @@ -30,7 +40,7 @@ auth sufficient pam_oauth2.so https://foo.org/oauth2/tokeninfo?access_token= uid And somebody is trying to login with login=foo and token=bar. -pam\_oauth2 module will make http request https://foo.org/oauth2/tokeninfo?access\_token=bar (tokeninfo url is simply concatenated with token) and check response code and content. +pam\_oauth2 module will make http request https://foo.org/oauth2/tokeninfo?access_token=bar (tokeninfo url is simply concatenated with token) and check response code and content. If the response code is not 200 - authentication will fail. After that it will check response content: @@ -55,6 +65,22 @@ It will check that response is a valid JSON object and top-level object contains If some keys haven't been found or values don't match with expectation - authentication will fail. + +### Sample configuration for [Keycloak](https://github.com/keycloak/keycloak) + +*As of Keycloak version 24.0.2* + +``` +auth sufficient pam_oauth2.so http://keycloak.local:8080/realm/master/protocol/openid-connect/token/introspect?token= client_id POST :Y2xpZW50X2lkOnNlY3JldA== +account sufficient pam_oauth2.so http://keycloak.local:8080/realm/master/protocol/openid-connect/token/introspect?token= client_id POST :Y2xpZW50X2lkOnNlY3JldA== +``` + +*Hint:* you can use the following command to obtain authorisation parameter from values of `client_id` and `secret`: + +``` +echo -n : | base64 +``` + ### Issues and Contributing Oauth2 PAM module welcomes questions via our [issues tracker](https://github.com/CyberDem0n/pam-oauth2/issues). We also greatly appreciate fixes, feature requests, and updates; before submitting a pull request, please visit our [contributor guidelines](https://github.com/CyberDem0n/pam-oauth2/blob/master/CONTRIBUTING.rst). diff --git a/pam_oauth2.c b/pam_oauth2.c index eaff93e..5589f58 100644 --- a/pam_oauth2.c +++ b/pam_oauth2.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -130,10 +131,14 @@ static int check_response(const struct response token_info, struct check_tokens return r; } -static int query_token_info(const char * const tokeninfo_url, const char * const authtok, long *response_code, struct response *token_info) { +static int query_token_info(const int post, const char * authz, + const char * const tokeninfo_url, + const char * const authtok, + long *response_code, struct response *token_info) { int ret = 1; char *url; CURL *session = curl_easy_init(); + struct curl_slist *list = NULL; if (!session) { syslog(LOG_AUTH|LOG_DEBUG, "pam_oauth2: can't initialize curl"); @@ -144,9 +149,34 @@ static int query_token_info(const char * const tokeninfo_url, const char * const strcpy(url, tokeninfo_url); strcat(url, authtok); - curl_easy_setopt(session, CURLOPT_URL, url); curl_easy_setopt(session, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(session, CURLOPT_WRITEDATA, token_info); + if (post) { + char *postfields = strrchr(url, (int)'?'); + if (postfields != NULL) { + *postfields++ = '\0'; + curl_easy_setopt(session, CURLOPT_POSTFIELDSIZE, strlen(postfields)); + curl_easy_setopt(session, CURLOPT_POSTFIELDS, postfields); + } + curl_easy_setopt(session, CURLOPT_POST, 1L); + } + curl_easy_setopt(session, CURLOPT_URL, url); + if (authz != NULL) { + size_t authz_len = strlen(authz); + { + char header[authz_len + 22]; + char *p = strchr(authz, (int)':'); + strcpy(header, "Authorization: Basic "); + if (NULL != p) { + strcpy(header + 15, authz); + header[15 + p - authz] = ' '; + } else { + strcat(header, authz); + } + list = curl_slist_append(list, header); + curl_easy_setopt(session, CURLOPT_HTTPHEADER, list); + } + } if (curl_easy_perform(session) == CURLE_OK && curl_easy_getinfo(session, CURLINFO_RESPONSE_CODE, response_code) == CURLE_OK) { @@ -156,6 +186,9 @@ static int query_token_info(const char * const tokeninfo_url, const char * const } free(url); + if (list != NULL) { + curl_slist_free_all(list); + } } else { syslog(LOG_AUTH|LOG_DEBUG, "pam_oauth2: memory allocation failed"); } @@ -165,7 +198,9 @@ static int query_token_info(const char * const tokeninfo_url, const char * const return ret; } -static int oauth2_authenticate(const char * const tokeninfo_url, const char * const authtok, struct check_tokens *ct) { +static int oauth2_authenticate(const int post, const char *authz, + const char * const tokeninfo_url, + const char * const authtok, struct check_tokens *ct) { struct response token_info; long response_code = 0; int ret; @@ -176,7 +211,7 @@ static int oauth2_authenticate(const char * const tokeninfo_url, const char * co } token_info.ptr[token_info.len = 0] = '\0'; - if (query_token_info(tokeninfo_url, authtok, &response_code, &token_info) != 0) { + if (query_token_info(post, authz, tokeninfo_url, authtok, &response_code, &token_info) != 0) { ret = PAM_AUTHINFO_UNAVAIL; } else if (response_code == 200) { ret = check_response(token_info, ct); @@ -191,9 +226,9 @@ static int oauth2_authenticate(const char * const tokeninfo_url, const char * co } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { - const char *tokeninfo_url = NULL, *authtok = NULL; + const char *tokeninfo_url = NULL, *authtok = NULL, *authz = NULL; struct check_tokens ct[argc]; - int i, ct_len = 1; + int i, ct_len = 1, post = 0; ct->key = ct->value = NULL; if (argc > 0) tokeninfo_url = argv[0]; @@ -225,17 +260,21 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, cons for (i = 2; i < argc; ++i) { const char *value = strchr(argv[i], '='); - if (value != NULL) { + if (argv[i][0] == ':') { + authz = &argv[i][1]; + } else if (value != NULL) { ct[ct_len].key = argv[i]; ct[ct_len].key_len = value - argv[i]; ct[ct_len].value = value + 1; ct[ct_len].value_len = strlen(value + 1); ct[ct_len++].match = 0; + } else { + post = (strcasecmp("POST", argv[i]) == 0); } } ct[ct_len].key = NULL; - return oauth2_authenticate(tokeninfo_url, authtok, ct); + return oauth2_authenticate(post, authz, tokeninfo_url, authtok, ct); } PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) {