#include "db-oauth2.h"
#include "dcrypt.h"
#include "dict.h"
+#include <time.h>
#undef DEF
#define DEF(type, name) \
SETTING_DEFINE_STRUCT_##type("oauth2_"#name, name, struct auth_oauth2_settings)
+#define WARN_AUD_FALLBACK_PERIOD 600
+static time_t last_warned_aud_fallback = 0;
+
static const struct setting_define auth_oauth2_setting_defines[] = {
DEF(STR, tokeninfo_url),
DEF(STR, grant_url),
DEF(STR, introspection_url),
DEF(BOOLLIST, scope),
+ DEF(BOOLLIST, audience),
DEF(ENUM, introspection_mode),
DEF(STR_NOVARS, username_validation_format),
DEF(STR, username_attribute),
.grant_url = "",
.introspection_url = "",
.scope = ARRAY_INIT,
+ .audience = ARRAY_INIT,
.force_introspection = FALSE,
.introspection_mode = ":auth:get:post:local",
.username_validation_format = "%{user}",
const char *value = auth_fields_find(req->fields, "scope");
bool has_scope = value != NULL;
- if (!has_scope)
+ if (!has_scope && array_is_empty(&req->db->set->audience)) {
value = auth_fields_find(req->fields, "aud");
+ if (value != NULL) {
+ time_t t0 = time(NULL);
+ if (t0 - last_warned_aud_fallback > WARN_AUD_FALLBACK_PERIOD) {
+ e_warning(authdb_event(req->auth_request),
+ "Token has no 'scope' claim; falling back to "
+ "'aud' for scope check is deprecated - "
+ "use oauth2_audience instead");
+ last_warned_aud_fallback = t0;
+ }
+ }
+ }
e_debug(authdb_event(req->auth_request),
"Token scope(s): %s", value);
return found;
}
+static bool
+db_oauth2_token_in_audience(struct db_oauth2_request *req,
+ enum passdb_result *result_r, const char **error_r)
+{
+ if (array_is_empty(&req->db->set->audience))
+ return TRUE;
+
+ const char *value = auth_fields_find(req->fields, "aud");
+ e_debug(authdb_event(req->auth_request),
+ "Token audience(s): %s", value != NULL ? value : "(none)");
+
+ bool found = FALSE;
+ if (value != NULL && *value != '\0') {
+ const char *const *entries = t_strsplit_tabescaped(value);
+ const char *wanted;
+ found = TRUE;
+ array_foreach_elem(&req->db->set->audience, wanted) {
+ if (!str_array_find(entries, wanted)) {
+ found = FALSE;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ *error_r = t_strdup_printf("Token audience does not include '%s'",
+ t_array_const_string_join(&req->db->set->audience, " "));
+ *result_r = PASSDB_RESULT_USER_DISABLED;
+ }
+ return found;
+}
+
static void db_oauth2_process_fields(struct db_oauth2_request *req,
enum passdb_result *result_r,
const char **error_r)
if (db_oauth2_user_is_enabled(req, result_r, error_r) &&
db_oauth2_validate_username(req, result_r, error_r) &&
- db_oauth2_token_in_scope(req, result_r, error_r)) {
+ db_oauth2_token_in_scope(req, result_r, error_r) &&
+ db_oauth2_token_in_audience(req, result_r, error_r)) {
/* The user has now been successfully authenticated,
mark the request as such. This allows having no
passdb in config. */