Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add POST method for token validation requests #13

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@ $ sudo make install
## Configuration

```
auth sufficient pam_oauth2.so <tokeninfo url> <login field> key1=value2 key2=value2
auth sufficient pam_oauth2.so <tokeninfo url> <login field> [POST] [:[<Authz scheme>:]<Authz parameters>] key1=value2 key2=value2
account sufficient pam_oauth2.so
```

Optional parameter `POST` indicates to use POST method when sending request to the `<tokeninfo url>`. This also adds
`Content-Type: application/x-www-form-urlencoded` header and moves CGI parameters from `<tokeninfo url>` into request body.
If this parameter is omitted, the GET method is used.

Optional parameter `:[<Authz scheme>:]<Authz parameters>` specify a field value for `Authorization` HTTP header to be sent along with `<tokeninfo url>`. If authorisation scheme `<Authz 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:
Expand All @@ -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:

Expand All @@ -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 <client_id>:<secret> | 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).
Expand Down
55 changes: 47 additions & 8 deletions pam_oauth2.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <curl/curl.h>
#include <security/pam_modules.h>
Expand Down Expand Up @@ -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");
Expand All @@ -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) {
Expand All @@ -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");
}
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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];
Expand Down Expand Up @@ -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) {
Expand Down