--- /dev/null
+/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "strnum.h"
+#include "http-client.h"
+#include "http-url.h"
+#include "json-parser.h"
+#include "oauth2.h"
+#include "oauth2-private.h"
+
+static void
+oauth2_passwd_grant_callback(struct oauth2_request *req,
+ struct oauth2_passwd_grant_result *res)
+{
+ i_assert(res->success == (res->error == NULL));
+ i_assert(req->pg_callback != NULL);
+ oauth2_passwd_grant_callback_t *callback = req->pg_callback;
+ req->pg_callback = NULL;
+ callback(res, req->pg_context);
+ oauth2_request_free_internal(req);
+}
+
+static void
+oauth2_passwd_grant_continue(struct oauth2_request *req, bool success,
+ const char *error)
+{
+ struct oauth2_passwd_grant_result res;
+ i_zero(&res);
+
+ i_assert(array_is_created(&req->fields) || !success);
+
+ res.success = success;
+ res.error = error;
+ res.valid = req->valid;
+
+ if (res.success) {
+ const struct oauth2_field *field;
+ /* see if we can figure out when it expires */
+ array_foreach(&req->fields, field) {
+ 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 = "Malformed number in expires_in";
+ } else {
+ res.expires_at = ioloop_time + expires_in;
+ }
+ break;
+ }
+ }
+ }
+
+ res.fields = &req->fields;
+
+ oauth2_passwd_grant_callback(req, &res);
+}
+
+static void
+oauth2_passwd_grant_response(const struct http_response *response,
+ struct oauth2_request *req)
+{
+ unsigned int status_1 = response->status / 100;
+
+ if (status_1 != 2 && status_1 != 4) {
+ oauth2_passwd_grant_continue(req, FALSE, response->reason);
+ } else {
+ if (status_1 == 2)
+ req->valid = TRUE;
+ else
+ req->valid = FALSE;
+ p_array_init(&req->fields, req->pool, 1);
+ /* 2xx is sufficient for token validation */
+ if (response->payload == NULL) {
+ oauth2_passwd_grant_continue(req, TRUE, NULL);
+ return;
+ }
+ req->is = response->payload;
+ i_stream_ref(req->is);
+ req->parser = json_parser_init(req->is);
+ req->json_parsed_cb = oauth2_passwd_grant_continue;
+ req->io = io_add_istream(req->is, oauth2_parse_json, req);
+ oauth2_parse_json(req);
+ }
+}
+
+#undef oauth2_passwd_grant_start
+struct oauth2_request *
+oauth2_passwd_grant_start(const struct oauth2_settings *set,
+ const struct oauth2_request_input *input,
+ const char *username,
+ const char *password,
+ oauth2_passwd_grant_callback_t *callback,
+ void *context)
+{
+ i_assert(oauth2_valid_token(input->token));
+
+ pool_t pool = pool_alloconly_create_clean("oauth2 passwd_grant", 1024);
+ struct oauth2_request *req =
+ p_new(pool, struct oauth2_request, 1);
+
+ req->pool = pool;
+ req->set = set;
+ req->pg_callback = callback;
+ req->pg_context = context;
+
+ req->req = http_client_request_url_str(req->set->client, "POST",
+ req->set->grant_url,
+ oauth2_passwd_grant_response,
+ req);
+ string_t *payload = str_new(pool, 128);
+ /* add token */
+ str_append(payload, "grant_type=password&username=");
+ http_url_escape_param(payload, username);
+ str_append(payload, "&password=");
+ http_url_escape_param(payload, password);
+ str_append(payload, "&client_id=");
+ http_url_escape_param(payload, req->set->client_id);
+ http_client_request_add_header(req->req, "Content-Type",
+ "application/x-www-form-urlencoded");
+ http_client_request_set_payload_data(req->req, payload->data, payload->used);
+
+ 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;
+}
/* GET tokeninfo from this URL, token is appended to URL
http://some.host/path?access_token= */
const char *tokeninfo_url;
+ /* POST grant password here, needs user credentials and client_* settings */
+ const char *grant_url;
/* GET more information from this URL, uses Bearer authentication */
const char *introspection_url;
/* POST refresh here, needs refresh token and client_* settings */
const char *refresh_url;
+ /* client identificator for oauth2 server */
const char *client_id;
+ /* client secret for oauth2 server */
const char *client_secret;
enum {
INTROSPECTION_MODE_GET_AUTH,
unsigned int timeout_msecs;
/* Should X-Dovecot-Auth-* headers be sent */
bool send_auth_headers;
+ /* Should use grant password mechanism for authentication */
+ bool use_grant_password;
+};
+
+struct oauth2_passwd_grant_result {
+ ARRAY_TYPE(oauth2_field) *fields;
+ const char *error;
+ time_t expires_at;
+ bool success:1;
+ bool valid:1;
};
struct oauth2_token_validation_result {
in_port_t local_port, real_local_port, remote_port, real_remote_port;
};
+typedef void
+oauth2_passwd_grant_callback_t(struct oauth2_passwd_grant_result*, void*);
+
typedef void
oauth2_token_validation_callback_t(struct oauth2_token_validation_result*, void*);
bool oauth2_valid_token(const char *token);
+struct oauth2_request*
+oauth2_passwd_grant_start(const struct oauth2_settings *set,
+ const struct oauth2_request_input *input,
+ const char *username,
+ const char *password,
+ oauth2_passwd_grant_callback_t *callback,
+ void *context);
+#define oauth2_passwd_grant_start(set, input, username, password, callback, context) \
+ oauth2_passwd_grant_start(set, input + \
+ CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_passwd_grant_result*, typeof(context))), \
+ username, password, \
+ (oauth2_passwd_grant_callback_t*)callback, (void*)context);
+
struct oauth2_request*
oauth2_token_validation_start(const struct oauth2_settings *set,
const struct oauth2_request_input *input,
CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_introspection_result*, typeof(context))), \
(oauth2_introspection_callback_t*)callback, (void*)context);
-struct oauth2_request*
+struct oauth2_request *
oauth2_refresh_start(const struct oauth2_settings *set,
const struct oauth2_request_input *input,
oauth2_refresh_callback_t *callback,