Skip to content

Commit

Permalink
lib-oauth2: Add support library for OAUTH2
Browse files Browse the repository at this point in the history
  • Loading branch information
cmouse authored and GitLab committed Feb 17, 2017
1 parent 678cbf4 commit a5e2288
Show file tree
Hide file tree
Showing 9 changed files with 703 additions and 2 deletions.
3 changes: 2 additions & 1 deletion configure.ac
Expand Up @@ -2630,7 +2630,7 @@ dnl **
dnl ** Shared libraries usage
dnl **

LIBDOVECOT_LA_LIBS='$(top_builddir)/src/lib-dict-extra/libdict_extra.la $(top_builddir)/src/lib-program-client/libprogram_client.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-stats/libstats.la $(top_builddir)/src/lib-http/libhttp.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-smtp/libsmtp.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-sasl/libsasl.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la $(top_builddir)/src/lib-dcrypt/libdcrypt.la $(top_builddir)/src/lib-test/libtest.la $(top_builddir)/src/lib/liblib.la'
LIBDOVECOT_LA_LIBS='$(top_builddir)/src/lib-dict-extra/libdict_extra.la $(top_builddir)/src/lib-oauth2/liboauth2.la $(top_builddir)/src/lib-program-client/libprogram_client.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-stats/libstats.la $(top_builddir)/src/lib-http/libhttp.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-smtp/libsmtp.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-sasl/libsasl.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la $(top_builddir)/src/lib-dcrypt/libdcrypt.la $(top_builddir)/src/lib-test/libtest.la $(top_builddir)/src/lib/liblib.la'
if test "$want_shared_libs" = "yes"; then
LIBDOVECOT_DEPS='$(top_builddir)/src/lib-dovecot/libdovecot.la'
LIBDOVECOT="$LIBDOVECOT_DEPS \$(MODULE_LIBS)"
Expand Down Expand Up @@ -2979,6 +2979,7 @@ src/lib-dns/Makefile
src/lib-fs/Makefile
src/lib-fts/Makefile
src/lib-http/Makefile
src/lib-oauth2/Makefile
src/lib-imap/Makefile
src/lib-imap-storage/Makefile
src/lib-imap-client/Makefile
Expand Down
3 changes: 2 additions & 1 deletion src/Makefile.am
Expand Up @@ -21,7 +21,8 @@ LIBDOVECOT_SUBDIRS = \
lib-smtp \
lib-imap \
lib-imap-storage \
lib-program-client
lib-program-client \
lib-oauth2

SUBDIRS = \
$(LIBDOVECOT_SUBDIRS) \
Expand Down
23 changes: 23 additions & 0 deletions src/lib-oauth2/Makefile.am
@@ -0,0 +1,23 @@
AM_CPPFLAGS = \
-I$(top_srcdir)/src/lib \
-I$(top_srcdir)/src/lib-http \
-I$(top_srcdir)/src/lib-settings

noinst_LTLIBRARIES=liboauth2.la

pkginc_libdir=$(pkgincludedir)
pkginc_lib_HEADERS = \
oauth2.h

noinst_HEADERS = \
oauth2-private.h

liboauth2_la_SOURCES = \
oauth2.c \
oauth2-token-validate.c \
oauth2-introspect.c \
oauth2-refresh.c

check_programs = \
oauth2-server \
test-oauth2
125 changes: 125 additions & 0 deletions src/lib-oauth2/oauth2-introspect.c
@@ -0,0 +1,125 @@
/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "str.h"
#include "ioloop.h"
#include "istream.h"
#include "http-client.h"
#include "http-url.h"
#include "json-parser.h"
#include "oauth2.h"
#include "oauth2-private.h"

static void
oauth2_introspection_callback(struct oauth2_request *req,
struct oauth2_introspection_result *res)
{
i_assert(res->success == (res->error == NULL));
i_assert(req->is_callback != NULL);
oauth2_introspection_callback_t *callback = req->is_callback;
req->is_callback = NULL;
callback(res, req->is_context);
oauth2_request_free_internal(req);
}

static void
oauth2_introspect_continue(struct oauth2_request *req, bool success,
const char *error)
{
struct oauth2_introspection_result res;
i_zero(&res);

res.success = success;
res.error = error;
res.fields = &req->fields;

oauth2_introspection_callback(req, &res);
}

static void
oauth2_introspect_response(const struct http_response *response,
struct oauth2_request *req)
{
if (response->status / 100 != 2) {
oauth2_introspect_continue(req, FALSE, response->reason);
} else {
if (response->payload == NULL) {
oauth2_introspect_continue(req, FALSE, "Missing response body");
return;
}
req->is = response->payload;
i_stream_ref(req->is);
req->parser = json_parser_init(req->is);
req->json_parsed_cb = oauth2_introspect_continue;
req->io = io_add_istream(req->is, oauth2_parse_json, req);
oauth2_parse_json(req);
}
}

#undef oauth2_introspection_start
struct oauth2_request*
oauth2_introspection_start(const struct oauth2_settings *set,
const struct oauth2_request_input *input,
oauth2_introspection_callback_t *callback,
void *context)
{
i_assert(oauth2_valid_token(input->token));

pool_t pool = pool_alloconly_create_clean("oauth2 introspection", 1024);
struct oauth2_request *req =
p_new(pool, struct oauth2_request, 1);
struct oauth2_introspection_result fail = {
.success = FALSE,
};
struct http_url *url;
const char *error;

req->pool = pool;
req->set = set;
req->is_callback = callback;
req->is_context = context;

string_t *enc = t_str_new(64);
str_append(enc, req->set->introspection_url);

if (set->introspection_mode == INTROSPECTION_MODE_GET) {
http_url_escape_param(enc, input->token);
}

if (http_url_parse(str_c(enc), NULL, 0, pool, &url, &error) < 0) {
fail.error = t_strdup_printf("http_url_parse(%s) failed: %s",
str_c(enc), error);
oauth2_introspection_callback(req, &fail);
return req;
}

if (set->introspection_mode == INTROSPECTION_MODE_POST) {
req->req = http_client_request_url(req->set->client, "POST", url,
oauth2_introspect_response,
req);
/* add token */
enc = t_str_new(strlen(input->token)+6);
str_append(enc, "token=");
http_url_escape_param(enc, input->token);
http_client_request_set_payload_data(req->req, enc->data, enc->used);
} else {
req->req = http_client_request_url(req->set->client, "GET", url,
oauth2_introspect_response,
req);
}

if (set->introspection_mode == INTROSPECTION_MODE_GET_AUTH)
http_client_request_add_header(req->req,
"Authorization",
t_strdup_printf("Bearer %s",
input->token));

oauth2_request_set_headers(req, input);

http_client_request_set_timeout_msecs(req->req,
req->set->timeout_msecs);
http_client_request_submit(req->req);

return req;
}

42 changes: 42 additions & 0 deletions src/lib-oauth2/oauth2-private.h
@@ -0,0 +1,42 @@
/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */
#ifndef OAUTH2_PRIVATE_H
#define OAUTH2_PRIVATE_H 1

struct oauth2_request {
pool_t pool;

const struct oauth2_settings *set;
struct http_client_request *req;
struct json_parser *parser;
struct istream *is;
struct io *io;

const char *username;

void (*json_parsed_cb)(struct oauth2_request*, bool success,
const char *error);

ARRAY_TYPE(oauth2_field) fields;
char *field_name;

oauth2_token_validation_callback_t *tv_callback;
void *tv_context;

oauth2_introspection_callback_t *is_callback;
void *is_context;

oauth2_refresh_callback_t *re_callback;
void *re_context;

/* indicates whether token is valid */
bool valid:1;
};

void oauth2_request_set_headers(struct oauth2_request *req,
const struct oauth2_request_input *input);

void oauth2_request_free_internal(struct oauth2_request *req);

void oauth2_parse_json(struct oauth2_request *req);

#endif
156 changes: 156 additions & 0 deletions src/lib-oauth2/oauth2-refresh.c
@@ -0,0 +1,156 @@
/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "ioloop.h"
#include "istream.h"
#include "str.h"
#include "http-client.h"
#include "http-url.h"
#include "json-parser.h"
#include "oauth2.h"
#include "oauth2-private.h"

static void
oauth2_refresh_callback(struct oauth2_request *req,
struct oauth2_refresh_result *res)
{
i_assert(res->success == (res->error == NULL));
i_assert(req->re_callback != NULL);
oauth2_refresh_callback_t *callback = req->re_callback;
req->re_callback = NULL;
callback(res, req->re_context);
oauth2_request_free_internal(req);
}

static bool
oauth2_refresh_field_parse(const struct oauth2_field *field,
struct oauth2_refresh_result *res)
{
if (strcasecmp(field->name, "expires_in") == 0) {
uint32_t expires_in = 0;
if (str_to_uint32(field->value, &expires_in) < 0) {
res->success = FALSE;
res->error = t_strdup_printf(
"Malformed number '%s' in expires_in",
field->value);
return FALSE;
} else {
res->expires_at = ioloop_time + expires_in;
}
} else if (strcasecmp(field->name, "token_type") == 0) {
if (strcasecmp(field->value,"bearer") != 0) {
res->success = FALSE;
res->error = t_strdup_printf(
"Expected Bearer token, got '%s'",
field->value);
return FALSE;
}
} else if (strcasecmp(field->name, "access_token") == 0) {
/* pooled memory */
res->bearer_token = field->value;
}
return TRUE;
}

static void
oauth2_refresh_continue(struct oauth2_request *req, bool success,
const char *error)
{
struct oauth2_refresh_result res;
i_zero(&res);

res.success = success;
res.error = error;

if (res.success) {
const struct oauth2_field *field;
/* see if we can figure out when it expires */
array_foreach(&req->fields, field) {
if (!oauth2_refresh_field_parse(field, &res))
break;
}
}

res.fields = &req->fields;

oauth2_refresh_callback(req, &res);
}

static void
oauth2_refresh_response(const struct http_response *response,
struct oauth2_request *req)
{
if (response->status / 100 != 2) {
oauth2_refresh_continue(req, FALSE, response->reason);
} else {
if (response->payload == NULL) {
oauth2_refresh_continue(req, FALSE, "Missing response body");
return;
}
req->is = response->payload;
i_stream_ref(req->is);
req->parser = json_parser_init(req->is);
req->json_parsed_cb = oauth2_refresh_continue;
req->io = io_add_istream(req->is, oauth2_parse_json, req);
oauth2_parse_json(req);
}
}

#undef oauth2_refresh_start
struct oauth2_request*
oauth2_refresh_start(const struct oauth2_settings *set,
const struct oauth2_request_input *input,
oauth2_refresh_callback_t *callback,
void *context)
{
i_assert(oauth2_valid_token(input->token));

pool_t pool = pool_alloconly_create_clean("oauth2 refresh", 1024);
struct oauth2_request *req =
p_new(pool, struct oauth2_request, 1);
struct http_url *url;
const char *error;
struct oauth2_refresh_result fail = {
.success = FALSE
};

req->pool = pool;
req->set = set;
req->re_callback = callback;
req->re_context = context;

const char *_url = req->set->refresh_url;

if (http_url_parse(_url, NULL, 0, pool, &url, &error) < 0) {
fail.error = t_strdup_printf("http_url_parse(%s) failed: %s",
_url, error);
oauth2_refresh_callback(req, &fail);
return req;
}

req->req = http_client_request_url(req->set->client, "POST", url,
oauth2_refresh_response,
req);
string_t *payload = str_new(req->pool, 128);
str_append(payload, "client_secret=");
http_url_escape_param(payload, req->set->client_secret);
str_append(payload, "&grant_type=refresh_token&refresh_token=");
http_url_escape_param(payload, input->token);
str_append(payload, "&client_id=");
http_url_escape_param(payload, req->set->client_id);

struct istream *is = i_stream_create_from_string(payload);

http_client_request_add_header(req->req, "Content-Type",
"application/x-www-form-urlencoded");

oauth2_request_set_headers(req, input);

http_client_request_set_payload(req->req, is, FALSE);
i_stream_unref(&is);
http_client_request_set_timeout_msecs(req->req,
req->set->timeout_msecs);
http_client_request_submit(req->req);

return req;
}

0 comments on commit a5e2288

Please sign in to comment.