diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore index 763f224..25bdf7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .idea +.vs +.vscode ngx_http_auth_jwt_module.so libjwt.so.0.6.0 libjwt.la @@ -9,3 +11,5 @@ jansson.pc libjwt.pc libjansson.so.4.10.0 libjwt.so.0.4.0 +vendor/**/* +build/**/* \ No newline at end of file diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..cd25cae --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": "Non ci sono configurazioni" +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..bb42b5b --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,7 @@ +{ + "ExpandedNodes": [ + "", + "\\src" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/ngx-http-auth-jwt-module/v16/.suo b/.vs/ngx-http-auth-jwt-module/v16/.suo new file mode 100644 index 0000000..2277eab Binary files /dev/null and b/.vs/ngx-http-auth-jwt-module/v16/.suo differ diff --git a/.vs/ngx-http-auth-jwt-module/v16/Browse.VC.db b/.vs/ngx-http-auth-jwt-module/v16/Browse.VC.db new file mode 100644 index 0000000..d9fe71c Binary files /dev/null and b/.vs/ngx-http-auth-jwt-module/v16/Browse.VC.db differ diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000..ec0265a Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..e241f59 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,31 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "windows-msvc-x64", + "configurationProvider": "ms-vscode.makefile-tools" + }, + { + "name": "Linux-x64", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu17", + "cppStandard": "gnu++14", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..77d2aef --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "files.associations": { + "*.gpx": "xml", + "ngx_http_auth_jwt_binary_converters.h": "c", + "jansson.h": "c", + "ngx_http.h": "c", + "type_traits": "c", + "stdio.h": "c", + "ngx_http_auth_jwt_string.h": "c", + "hashset.h": "c", + "ngx_core.h": "c", + "jwt.h": "c", + "ngx_config.h": "c" + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a031ac2..757c157 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM centos:7 LABEL maintainer="TeslaGov" email="developers@teslagov.com" -ARG NGINX_VERSION=1.16.1 +ARG NGINX_VERSION=1.23.0 ARG JANSSON_VERSION=2.10 ARG LIBJWT_VERSION=1.9.0 diff --git a/config b/config index 8b9c475..686307f 100644 --- a/config +++ b/config @@ -1,6 +1,6 @@ ngx_addon_name=ngx_http_auth_jwt_module ngx_module_type=HTTP ngx_module_name=ngx_http_auth_jwt_module -ngx_module_srcs="$ngx_addon_dir/src/ngx_http_auth_jwt_binary_converters.c $ngx_addon_dir/src/ngx_http_auth_jwt_header_processing.c $ngx_addon_dir/src/ngx_http_auth_jwt_string.c $ngx_addon_dir/src/ngx_http_auth_jwt_module.c" +ngx_module_srcs="$ngx_addon_dir/src/ngx_http_auth_jwt_binary_converters.c $ngx_addon_dir/src/ngx_http_auth_jwt_header_processing.c $ngx_addon_dir/src/ngx_http_auth_jwt_string.c $ngx_addon_dir/src/hashset.c $ngx_addon_dir/src/ngx_http_auth_jwt_module.c" ngx_module_libs="-ljansson -ljwt" . auto/module \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..a5c5ff8 --- /dev/null +++ b/setup.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -o nounset +set -o errexit + +DIR=$(pwd) +BUILDDIR=$DIR/build +NGINX_DIR=nginx +NGINX_VERSION=1.23.0 + +clean () { + rm -rf build vendor +} + +setup_local_directories () { + if [ ! -d $BUILDDIR ]; then + mkdir $BUILDDIR > /dev/null 2>&1 + mkdir $BUILDDIR/$NGINX_DIR > /dev/null 2>&1 + fi + + if [ ! -d "vendor" ]; then + mkdir vendor > /dev/null 2>&1 + fi +} + +install_nginx () { + if [ ! -d "vendor/nginx-$NGINX_VERSION" ]; then + pushd vendor > /dev/null 2>&1 + curl -s -L -O "http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz" + tar xzf "nginx-$NGINX_VERSION.tar.gz" + pushd "nginx-$NGINX_VERSION" > /dev/null 2>&1 + ./configure \ + --with-debug \ + --prefix=$(pwd)/../../build/nginx \ + --conf-path=conf/nginx.conf \ + --error-log-path=logs/error.log \ + --http-log-path=logs/access.log + make + make install + popd > /dev/null 2>&1 + popd > /dev/null 2>&1 + ln -sf $(pwd)/nginx.conf $(pwd)/build/nginx/conf/nginx.conf + else + printf "NGINX already installed\n" + fi +} + +if [[ "$#" -eq 1 ]]; then + if [[ "$1" == "clean" ]]; then + clean + else + echo "clean is the only option" + fi +else + setup_local_directories + install_nginx +fi \ No newline at end of file diff --git a/src/hashset.c b/src/hashset.c new file mode 100644 index 0000000..0baf461 --- /dev/null +++ b/src/hashset.c @@ -0,0 +1,72 @@ +#include "hashset.h" +#include + +size_t fnv1a_hash(const char *cp) +{ + size_t hash = 0x811c9dc5; + + while (*cp) + { + hash ^= (unsigned char)toupper(*cp++); + hash *= 0x01000193; + } + + return hash; +} + +ngx_flag_t hashset_contains(hashset_t *set, const char *item) +{ + size_t hash, index; + hashset_entry_t *entry; + + hash = fnv1a_hash(item); + index = set->buckets[hash % set->capacity] - 1; + + while (index < set->capacity) + { + entry = &set->entries[index]; + if (entry->hash == hash && ngx_strcasecmp((u_char *)entry->value, (u_char *)item) == 0) + { + return 1; + } + + index = entry->next; + } + + return 0; +} + +ngx_flag_t hashset_add(hashset_t *set, const char *item) +{ + size_t hash, bucket_index; + size_t *bucket; + hashset_entry_t *entry; + + //we don't need the set to grow + if (set->nentries == set->capacity) + { + return 0; + } + + hash = fnv1a_hash(item); + bucket = &set->buckets[hash % set->capacity]; + bucket_index = *bucket - 1; + + while (bucket_index < set->capacity) + { + entry = &set->entries[bucket_index]; + if (entry->hash == hash && ngx_strcasecmp((u_char *)entry->value, (u_char *)item) == 0) + { + return 0; + } + + bucket_index = entry->next; + } + + entry = &set->entries[set->nentries]; + entry->hash = hash; + entry->value = item; + entry->next = *bucket - 1; + *bucket = ++set->nentries; + return 1; +} \ No newline at end of file diff --git a/src/hashset.h b/src/hashset.h new file mode 100644 index 0000000..cd5ca04 --- /dev/null +++ b/src/hashset.h @@ -0,0 +1,19 @@ +#include + +typedef struct +{ + size_t hash; + const char *value; + size_t next; +} hashset_entry_t; + +typedef struct +{ + size_t capacity; + size_t *buckets; + hashset_entry_t *entries; + size_t nentries; +} hashset_t; + +ngx_flag_t hashset_add(hashset_t *set, const char *item); +ngx_flag_t hashset_contains(hashset_t *set, const char *item); \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_binary_converters.c b/src/ngx_http_auth_jwt_binary_converters.c index 8aea970..d23811a 100644 --- a/src/ngx_http_auth_jwt_binary_converters.c +++ b/src/ngx_http_auth_jwt_binary_converters.c @@ -11,39 +11,47 @@ #include -int hex_char_to_binary( char ch, char* ret ) +int hex_char_to_binary(char ch, char *ret) { - ch = tolower( ch ); - if( isdigit( ch ) ) + ch = tolower(ch); + if (isdigit(ch)) + { *ret = ch - '0'; - else if( ch >= 'a' && ch <= 'f' ) - *ret = ( ch - 'a' ) + 10; - else if( ch >= 'A' && ch <= 'F' ) - *ret = ( ch - 'A' ) + 10; + } + else if (ch >= 'a' && ch <= 'f') + { + *ret = (ch - 'a') + 10; + } + else if (ch >= 'A' && ch <= 'F') + { + *ret = (ch - 'A') + 10; + } else + { return *ret = 0; + } + return 1; } -int hex_to_binary( const char* str, u_char* buf, int len ) +int hex_to_binary(const char *str, u_char *buf, int len) { - u_char - *cpy = buf; - char - low, - high; - int - odd = len % 2; - - if (odd) { + u_char *cpy = buf; + char low, high; + int odd = len % 2; + + if (odd) + { return -1; } - for (int i = 0; i < len; i += 2) { - hex_char_to_binary( *(str + i), &high ); - hex_char_to_binary( *(str + i + 1 ), &low ); - + for (int i = 0; i < len; i += 2) + { + hex_char_to_binary(*(str + i), &high); + hex_char_to_binary(*(str + i + 1), &low); + *cpy++ = low | (high << 4); } + return 0; } \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_header_processing.c b/src/ngx_http_auth_jwt_header_processing.c index e368c84..fafbd47 100644 --- a/src/ngx_http_auth_jwt_header_processing.c +++ b/src/ngx_http_auth_jwt_header_processing.c @@ -16,18 +16,18 @@ * Sample code from nginx. * https://www.nginx.com/resources/wiki/start/topics/examples/headers_management/?highlight=http%20settings */ -ngx_table_elt_t* search_headers_in(ngx_http_request_t *r, u_char *name, size_t len) +ngx_table_elt_t *search_headers_in(ngx_http_request_t *r, ngx_str_t *name) { - ngx_list_part_t *part; - ngx_table_elt_t *h; - ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_uint_t i; // Get the first part of the list. There is usual only one part. part = &r->headers_in.headers.part; h = part->elts; // Headers list array may consist of more than one part, so loop through all of it - for (i = 0; /* void */ ; i++) + for (i = 0; /* void */; i++) { if (i >= part->nelts) { @@ -43,7 +43,7 @@ ngx_table_elt_t* search_headers_in(ngx_http_request_t *r, u_char *name, size_t l } //Just compare the lengths and then the names case insensitively. - if (len != h[i].key.len || ngx_strcasecmp(name, h[i].key.data) != 0) + if (name->len != h[i].key.len || ngx_strcasecmp(name->data, h[i].key.data) != 0) { /* This header doesn't match. */ continue; @@ -65,31 +65,33 @@ ngx_table_elt_t* search_headers_in(ngx_http_request_t *r, u_char *name, size_t l * Sample code from nginx * https://www.nginx.com/resources/wiki/start/topics/examples/headers_management/#how-can-i-set-a-header */ -ngx_int_t set_custom_header_in_headers_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) { - ngx_table_elt_t *h; +ngx_int_t set_custom_header_in_headers_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) +{ + ngx_table_elt_t *h; - /* + /* All we have to do is just to allocate the header... */ - h = ngx_list_push(&r->headers_out.headers); - if (h == NULL) { - return NGX_ERROR; - } + h = ngx_list_push(&r->headers_out.headers); + if (h == NULL) + { + return NGX_ERROR; + } - /* + /* ... setup the header key ... */ - h->key = *key; + h->key = *key; - /* + /* ... and the value. */ - h->value = *value; + h->value = *value; - /* + /* Mark the header as not deleted. */ - h->hash = 1; + h->hash = 1; - return NGX_OK; + return NGX_OK; } \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_header_processing.h b/src/ngx_http_auth_jwt_header_processing.h index 0b64133..2eaada4 100644 --- a/src/ngx_http_auth_jwt_header_processing.h +++ b/src/ngx_http_auth_jwt_header_processing.h @@ -8,7 +8,7 @@ #ifndef _NGX_HTTP_AUTH_JWT_HEADER_PROCESSING_H #define _NGX_HTTP_AUTH_JWT_HEADER_PROCESSING_H -ngx_table_elt_t* search_headers_in(ngx_http_request_t *r, u_char *name, size_t len); +ngx_table_elt_t* search_headers_in(ngx_http_request_t *r, ngx_str_t * name); ngx_int_t set_custom_header_in_headers_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value); #endif /* _NGX_HTTP_AUTH_JWT_HEADER_PROCESSING_H */ \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index ec8278a..3383733 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -19,153 +19,202 @@ #include "ngx_http_auth_jwt_string.h" #include +#include "hashset.h" -const char* KEY_FILE_PATH = "/app/pub_key"; +typedef char *(*ngx_http_auth_jwt_access_pt)(ngx_http_request_t *r, ngx_str_t *context); -typedef struct { - ngx_str_t auth_jwt_loginurl; - ngx_str_t auth_jwt_key; - ngx_flag_t auth_jwt_enabled; - ngx_flag_t auth_jwt_redirect; - ngx_str_t auth_jwt_validation_type; - ngx_str_t auth_jwt_algorithm; - ngx_flag_t auth_jwt_use_keyfile; - ngx_str_t auth_jwt_keyfile_path; - ngx_flag_t auth_jwt_validate_email; +typedef enum +{ + ACCESS_TYPE_ALLOW = 0, + ACCESS_TYPE_DENY = 1 +} ngx_http_auth_jwt_policy_access_type_t; + +typedef struct +{ + ngx_http_auth_jwt_policy_access_type_t access_type; + ngx_array_t *users; + ngx_array_t *roles; +} ngx_http_auth_jwt_policy_t; + +typedef struct +{ + ngx_str_t grant; + ngx_str_t header; + ngx_flag_t replace; +} ngx_http_auth_jwt_grant_mapping_t; + +typedef struct +{ + ngx_http_auth_jwt_access_pt handler; + ngx_str_t context; +} ngx_http_auth_jwt_accessor_t; + +typedef struct +{ + ngx_str_t key_source; + ngx_flag_t enabled; + ngx_str_t validation_type; + ngx_str_t algorithm; + ngx_str_t keyfile_path; + ngx_str_t name_grant; + ngx_str_t role_grant; + ngx_array_t *policies; + ngx_array_t *grant_header_mappings; + ngx_str_t key; + ngx_http_auth_jwt_accessor_t jwt_accessor; } ngx_http_auth_jwt_loc_conf_t; static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf); static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r); -static void * ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf); -static char * ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); -static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type); +static ngx_int_t ngx_http_auth_set_headers(ngx_http_request_t *r, jwt_t *jwt, ngx_http_auth_jwt_loc_conf_t *jwt_cf); +static void *ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); +static char *get_jwt_from_header(ngx_http_request_t *r, ngx_str_t *context); +static char *get_jwt_from_cookie(ngx_http_request_t *r, ngx_str_t *context); +static char *get_jwt_from_url(ngx_http_request_t *r, ngx_str_t *context); +static ngx_flag_t matches_jwt_policy_n(ngx_http_request_t *r, const char *user, json_t *json_roles, size_t json_roles_count, ngx_array_t *policies); +static ngx_flag_t matches_jwt_policy(ngx_http_request_t *r, const char *user, const char *role, ngx_array_t *policies); +static ngx_flag_t validate_jwt_token_policies(ngx_http_request_t *r, jwt_t *jwt, ngx_http_auth_jwt_loc_conf_t *jwt_cf); +static char *ngx_http_auth_jwt_add_policy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_auth_jwt_add_grant_header_mapping(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_auth_jwt_commands[] = { - - { ngx_string("auth_jwt_loginurl"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_loginurl), - NULL }, - - { ngx_string("auth_jwt_key"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_key), - NULL }, - - { ngx_string("auth_jwt_enabled"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_enabled), - NULL }, - - { ngx_string("auth_jwt_use_keyfile"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_use_keyfile), - NULL }, - - { ngx_string("auth_jwt_keyfile_path"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_keyfile_path), - NULL }, - - { ngx_string("auth_jwt_redirect"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_redirect), - NULL }, - - { ngx_string("auth_jwt_validation_type"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_validation_type), - NULL }, - - { ngx_string("auth_jwt_algorithm"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_algorithm), - NULL }, - - { ngx_string("auth_jwt_validate_email"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_validate_email), - NULL }, - - ngx_null_command -}; - + {ngx_string("auth_jwt_key"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, key_source), + NULL}, + + {ngx_string("auth_jwt_enabled"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, enabled), + NULL}, + + {ngx_string("auth_jwt_keyfile_path"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, keyfile_path), + NULL}, + + {ngx_string("auth_jwt_validation_type"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, validation_type), + NULL}, + + {ngx_string("auth_jwt_algorithm"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, algorithm), + NULL}, + + {ngx_string("auth_jwt_name_grant"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, name_grant), + NULL}, + + {ngx_string("auth_jwt_role_grant"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, role_grant), + NULL}, + + {ngx_string("auth_jwt_policy"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE23, + ngx_http_auth_jwt_add_policy, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, policies), + NULL}, + + {ngx_string("auth_jwt_grant_header_mapping"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE23, + ngx_http_auth_jwt_add_grant_header_mapping, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_jwt_loc_conf_t, grant_header_mappings), + NULL}, + + ngx_null_command}; static ngx_http_module_t ngx_http_auth_jwt_module_ctx = { - NULL, /* preconfiguration */ - ngx_http_auth_jwt_init, /* postconfiguration */ + NULL, /* preconfiguration */ + ngx_http_auth_jwt_init, /* postconfiguration */ - NULL, /* create main configuration */ - NULL, /* init main configuration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ - NULL, /* create server configuration */ - NULL, /* merge server configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ - ngx_http_auth_jwt_create_loc_conf, /* create location configuration */ - ngx_http_auth_jwt_merge_loc_conf /* merge location configuration */ + ngx_http_auth_jwt_create_loc_conf, /* create location configuration */ + ngx_http_auth_jwt_merge_loc_conf /* merge location configuration */ }; - ngx_module_t ngx_http_auth_jwt_module = { NGX_MODULE_V1, - &ngx_http_auth_jwt_module_ctx, /* module context */ - ngx_http_auth_jwt_commands, /* module directives */ - NGX_HTTP_MODULE, /* module type */ - NULL, /* init master */ - NULL, /* init module */ - NULL, /* init process */ - NULL, /* init thread */ - NULL, /* exit thread */ - NULL, /* exit process */ - NULL, /* exit master */ - NGX_MODULE_V1_PADDING -}; + &ngx_http_auth_jwt_module_ctx, /* module context */ + ngx_http_auth_jwt_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING}; + +inline static void ngx_log_policy(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, ngx_http_auth_jwt_policy_t *policy) +{ + char *type; + char users[NGX_MAX_ERROR_STR]; + char roles[NGX_MAX_ERROR_STR]; + + if (policy->access_type == ACCESS_TYPE_ALLOW) + { + type = "allow"; + } + else + { + type = "deny"; + } + + ngx_memzero(users, sizeof(users)); + ngx_memzero(roles, sizeof(roles)); + ngx_str_t str; + str.len = NGX_MAX_ERROR_STR; + + str.data = (u_char *)users; + ngx_str_join(policy->users, &str, ","); + + str.data = (u_char *)roles; + ngx_str_join(policy->roles, &str, ","); + + ngx_log_error(level, log, err, "validating policy with type=%s, users=[%s], roles=[%s]", type, users, roles); +} static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) { - ngx_str_t useridHeaderName = ngx_string("x-userid"); - ngx_str_t emailHeaderName = ngx_string("x-email"); - char* jwtCookieValChrPtr; - char* return_url; - ngx_http_auth_jwt_loc_conf_t *jwtcf; - u_char *keyBinary; - // For clearing it later on - char* pub_key = NULL; jwt_t *jwt = NULL; - int jwtParseReturnCode; + char *jwt_value; + ngx_http_auth_jwt_loc_conf_t *jwt_cf; + int jwt_parse_result; jwt_alg_t alg; - const char* sub; - const char* email; - ngx_str_t sub_t; - ngx_str_t email_t; time_t exp; time_t now; - ngx_str_t auth_jwt_algorithm; - int keylen; - - jwtcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_jwt_module); - - if (!jwtcf->auth_jwt_enabled) + + jwt_cf = ngx_http_get_module_loc_conf(r, ngx_http_auth_jwt_module); + if (!jwt_cf->enabled) { return NGX_DECLINED; } @@ -175,349 +224,816 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) { return NGX_DECLINED; } - - jwtCookieValChrPtr = getJwt(r, jwtcf->auth_jwt_validation_type); - if (jwtCookieValChrPtr == NULL) + + jwt_value = jwt_cf->jwt_accessor.handler(r, &jwt_cf->jwt_accessor.context); + if (jwt_value == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a jwt"); - goto redirect; + goto unauthorized; } - - // convert key from hex to binary, if a symmetric key - auth_jwt_algorithm = jwtcf->auth_jwt_algorithm; - if (auth_jwt_algorithm.len == 0 || (auth_jwt_algorithm.len == sizeof("HS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "HS256", sizeof("HS256") - 1)==0)) + // validate the jwt + jwt_parse_result = jwt_decode(&jwt, jwt_value, jwt_cf->key.data, jwt_cf->key.len); + if (jwt_parse_result != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt (%d)", jwt_parse_result); + goto unauthorized; + } + + // validate the algorithm + alg = jwt_get_alg(jwt); + if (alg != JWT_ALG_HS256 && alg != JWT_ALG_RS256) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in jwt %d", alg); + goto unauthorized; + } + + // validate the exp date of the JWT + exp = (time_t)jwt_get_grant_int(jwt, "exp"); + now = time(NULL); + if (exp < now) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt has expired"); + goto unauthorized; + } + +#if NGX_DEBUG + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "found valid jwt token: %s", jwt_value); +#endif + + if (validate_jwt_token_policies(r, jwt, jwt_cf) == 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "jwt policy validation failed"); + goto unauthorized; + } + + if (ngx_http_auth_set_headers(r, jwt, jwt_cf) == 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "an error occurred mapping grants"); + goto unauthorized; + } + + jwt_free(jwt); + + return NGX_OK; + +unauthorized: + if (jwt) + { + jwt_free(jwt); + } + + return NGX_HTTP_UNAUTHORIZED; +} + +static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf) +{ + ngx_http_handler_pt *h; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); + if (h == NULL) + { + return NGX_ERROR; + } + + *h = ngx_http_auth_jwt_handler; + return NGX_OK; +} + +static void *ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_auth_jwt_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_jwt_loc_conf_t)); + if (conf == NULL) + { + return NULL; + } + + // set the flag to unset + conf->enabled = (ngx_flag_t)-1; + conf->policies = NGX_CONF_UNSET_PTR; + conf->grant_header_mappings = NGX_CONF_UNSET_PTR; + + ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "Created Location Configuration"); + + return conf; +} + +static char *ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_auth_jwt_loc_conf_t *prev = parent; + ngx_http_auth_jwt_loc_conf_t *conf = child; + + ngx_conf_merge_str_value(conf->key_source, prev->key_source, ""); + ngx_conf_merge_str_value(conf->validation_type, prev->validation_type, ""); + ngx_conf_merge_str_value(conf->algorithm, prev->algorithm, "HS256"); + ngx_conf_merge_str_value(conf->keyfile_path, prev->keyfile_path, ""); + ngx_conf_merge_str_value(conf->name_grant, prev->name_grant, "sub"); + ngx_conf_merge_str_value(conf->role_grant, prev->role_grant, "role"); + ngx_conf_merge_ptr_value(conf->policies, prev->policies, NULL); + ngx_conf_merge_ptr_value(conf->grant_header_mappings, prev->grant_header_mappings, NULL); + + if (conf->enabled == ((ngx_flag_t)-1)) + { + conf->enabled = (prev->enabled == ((ngx_flag_t)-1)) ? 0 : prev->enabled; + } + + // convert key from hex to binary, if a symmetric key + if (conf->algorithm.len == 0 || (conf->algorithm.len == sizeof("HS256") - 1 && ngx_strncmp(conf->algorithm.data, "HS256", sizeof("HS256") - 1) == 0)) { - keylen = jwtcf->auth_jwt_key.len / 2; - keyBinary = ngx_palloc(r->pool, keylen); - if (0 != hex_to_binary((char *)jwtcf->auth_jwt_key.data, keyBinary, jwtcf->auth_jwt_key.len)) + conf->key.len = conf->key_source.len / 2; + conf->key.data = ngx_palloc(cf->pool, conf->key.len); + + if (hex_to_binary((char *)conf->key_source.data, conf->key.data, conf->key_source.len) != 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to turn hex key into binary"); - goto redirect; + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "failed to turn hex key into binary"); + return NGX_CONF_ERROR; } } - else if ( auth_jwt_algorithm.len == sizeof("RS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "RS256", sizeof("RS256") - 1) == 0 ) + else if (conf->algorithm.len == sizeof("RS256") - 1 && ngx_strncmp(conf->algorithm.data, "RS256", sizeof("RS256") - 1) == 0) { // in this case, 'Binary' is a misnomer, as it is the public key string itself - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a jwt"); - if (jwtcf->auth_jwt_use_keyfile == 1) + if (conf->keyfile_path.len > 0) { - FILE *file = fopen((const char*)jwtcf->auth_jwt_keyfile_path.data, "rb"); + FILE *file = fopen((const char *)conf->keyfile_path.data, "rb"); // Check if file exists or is correctly opened if (file == NULL) { - char err[100]; - strcpy(err, "failed to open pub key file: "); - strcat(err, KEY_FILE_PATH); - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, err); - goto redirect; + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "failed to open pub key file '%s'", (const char *)conf->keyfile_path.data); + return NGX_CONF_ERROR; } // Read file length fseek(file, 0, SEEK_END); long key_size = ftell(file); fseek(file, 0, SEEK_SET); - + if (key_size == 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid key file size, check the key file"); - goto redirect; + fclose(file); + + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "invalid key file size, check the key file"); + return NGX_CONF_ERROR; } // Read pub key - pub_key = malloc(key_size + 1); - size_t bytes_read = fread(pub_key, 1, key_size, file); - fclose(file); + conf->key.len = (size_t)key_size; + conf->key.data = ngx_palloc(cf->pool, key_size); + + long readBytes = fread(conf->key.data, 1, key_size, file); + if (readBytes < key_size) + { + fclose(file); + + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "an error occurred reading the key file"); + return NGX_CONF_ERROR; + } - keyBinary = (u_char*)pub_key; - keylen = (int)key_size; + fclose(file); } else { - keyBinary = jwtcf->auth_jwt_key.data; - keylen = jwtcf->auth_jwt_key.len; + conf->key.data = conf->key_source.data; + conf->key.len = conf->key_source.len; } } else { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unsupported algorithm"); - goto redirect; + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "unsupported algorithm"); + return NGX_CONF_ERROR; } - - // validate the jwt - jwtParseReturnCode = jwt_decode(&jwt, jwtCookieValChrPtr, keyBinary, keylen); - if (jwtParseReturnCode != 0) + + if (conf->validation_type.len == 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt"); - goto redirect; + conf->jwt_accessor.handler = get_jwt_from_header; + conf->jwt_accessor.context.data = (u_char *)"Authorization"; + conf->jwt_accessor.context.len = sizeof("Authorization") - 1; + + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "Serching for jwt in header 'Authorization'"); } - - // validate the algorithm - alg = jwt_get_alg(jwt); - if (alg != JWT_ALG_HS256 && alg != JWT_ALG_RS256) + else if (conf->validation_type.len >= (sizeof("HEADER=") - 1) && ngx_strncmp(conf->validation_type.data, "HEADER=", (sizeof("HEADER=") - 1)) == 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in jwt %d", alg); - goto redirect; + conf->jwt_accessor.handler = get_jwt_from_header; + conf->jwt_accessor.context.len = conf->validation_type.len - (sizeof("HEADER=") - 1); + + if (conf->jwt_accessor.context.len == 0) + { + conf->jwt_accessor.context.data = (u_char *)"Authorization"; + conf->jwt_accessor.context.len = sizeof("Authorization") - 1; + } + else + { + conf->jwt_accessor.context.data = conf->validation_type.data + (sizeof("HEADER=") - 1); + } + + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "Serching for jwt in header '%s'", conf->jwt_accessor.context.data); } - - // validate the exp date of the JWT - exp = (time_t)jwt_get_grant_int(jwt, "exp"); - now = time(NULL); - if (exp < now) + else if (conf->validation_type.len >= (sizeof("COOKIE=") - 1) && ngx_strncmp(conf->validation_type.data, "COOKIE=", (sizeof("COOKIE=") - 1)) == 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt has expired"); - goto redirect; - } + conf->jwt_accessor.handler = get_jwt_from_cookie; + conf->jwt_accessor.context.len = conf->validation_type.len - (sizeof("COOKIE=") - 1); - // extract the userid - sub = jwt_get_grant(jwt, "sub"); - if (sub == NULL) + if (conf->jwt_accessor.context.len == 0) + { + conf->jwt_accessor.context.data = (u_char *)"access_token"; + conf->jwt_accessor.context.len = sizeof("access_token") - 1; + } + else + { + conf->jwt_accessor.context.data = conf->validation_type.data + (sizeof("COOKIE=") - 1); + } + + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "Serching for jwt in cookie '%s'", conf->jwt_accessor.context.data); + } + else if (conf->validation_type.len >= (sizeof("URL=") - 1) && ngx_strncmp(conf->validation_type.data, "URL=", (sizeof("URL=") - 1)) == 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain a subject"); + conf->jwt_accessor.handler = get_jwt_from_url; + conf->jwt_accessor.context.len = conf->validation_type.len - (sizeof("URL=") - 1); + + if (conf->jwt_accessor.context.len == 0) + { + conf->jwt_accessor.context.data = (u_char *)"access_token"; + conf->jwt_accessor.context.len = sizeof("access_token") - 1; + } + else + { + conf->jwt_accessor.context.data = conf->validation_type.data + (sizeof("URL=") - 1); + } + + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "Serching for jwt in url param '%s'", conf->jwt_accessor.context.data); } else { - sub_t = ngx_char_ptr_to_str_t(r->pool, (char *)sub); - set_custom_header_in_headers_out(r, &useridHeaderName, &sub_t); + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "unsupported validation type"); + return NGX_CONF_ERROR; } - if (jwtcf->auth_jwt_validate_email == 1) + // TODO: merge policies + + return NGX_CONF_OK; +} + +static char *ngx_http_auth_jwt_add_policy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_str_t *sAccessType; + ngx_array_t **policies; + ngx_array_t *users = NULL; + ngx_array_t *roles = NULL; + ngx_http_auth_jwt_policy_t *policy; + ngx_http_auth_jwt_policy_access_type_t accessType; + + policies = (ngx_array_t **)((char *)conf + cmd->offset); + + if (*policies == NGX_CONF_UNSET_PTR) { - email = jwt_get_grant(jwt, "emailAddress"); - if (email == NULL) + *policies = ngx_array_create(cf->pool, 1, sizeof(ngx_http_auth_jwt_policy_t)); + if (*policies == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain an email address"); + return NGX_CONF_ERROR; } - else + } + + sAccessType = &((ngx_str_t *)cf->args->elts)[1]; + if (sAccessType->len == 0 || ngx_strcasecmp(sAccessType->data, (u_char *)"allow") == 0) + { + accessType = ACCESS_TYPE_ALLOW; + } + else if (ngx_strcasecmp(sAccessType->data, (u_char *)"deny") == 0) + { + accessType = ACCESS_TYPE_DENY; + } + else + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid value \"%s\" in \"%s\" directive, " + "it must be \"allow\" or \"deny\"", + sAccessType->data, cmd->name.data); + return NGX_CONF_ERROR; + } + + users = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); + if (users == NULL) + { + goto error; + } + + if (ngx_str_split(&((ngx_str_t *)cf->args->elts)[2], users, ",") != NGX_OK) + { + goto error; + } + + roles = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); + if (roles == NULL) + { + goto error; + } + + if (cf->args->nelts == 4) + { + if (ngx_str_split(&((ngx_str_t *)cf->args->elts)[3], roles, ",") != NGX_OK) { - email_t = ngx_char_ptr_to_str_t(r->pool, (char *)email); - set_custom_header_in_headers_out(r, &emailHeaderName, &email_t); + goto error; } } - jwt_free(jwt); + // Check for empty policy + if (users->nelts == 0 && roles->nelts == 0) + { +#if NGX_DEBUG + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "found empty policy (%d)"); +#endif - if (pub_key == NULL) free(pub_key); - - return NGX_OK; - - redirect: + ngx_array_destroy(users); + ngx_array_destroy(roles); + return NGX_CONF_OK; + } + + policy = (ngx_http_auth_jwt_policy_t *)ngx_array_push(*policies); + if (policy == NULL) + { + goto error; + } + +#if NGX_DEBUG + size_t i; - if (jwt) + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "found valid policy (%d) for users (%d) and roles (%d)", accessType, users->nelts, roles->nelts); + + if (accessType == ACCESS_TYPE_ALLOW) + { + for (i = 0; i < users->nelts; i++) { - jwt_free(jwt); + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "users (%s) is allowed", ((ngx_str_t *)users->elts)[i].data); } - r->headers_out.location = ngx_list_push(&r->headers_out.headers); - - if (r->headers_out.location == NULL) + for (i = 0; i < roles->nelts; i++) { - ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "role (%s) is allowed", ((ngx_str_t *)roles->elts)[i].data); + } + } + else + { + for (i = 0; i < users->nelts; i++) + { + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "users (%s) is denied", ((ngx_str_t *)users->elts)[i].data); } - r->headers_out.location->hash = 1; - r->headers_out.location->key.len = sizeof("Location") - 1; - r->headers_out.location->key.data = (u_char *) "Location"; - - if (r->method == NGX_HTTP_GET) + for (i = 0; i < roles->nelts; i++) { - int loginlen; - char * scheme; - ngx_str_t server; - ngx_str_t uri_variable_name = ngx_string("request_uri"); - ngx_int_t uri_variable_hash; - ngx_http_variable_value_t * request_uri_var; - ngx_str_t uri; - ngx_str_t uri_escaped; - uintptr_t escaped_len; + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "role (%s) is denied", ((ngx_str_t *)roles->elts)[i].data); + } + } +#endif - loginlen = jwtcf->auth_jwt_loginurl.len; + policy->access_type = accessType; + policy->users = users; + policy->roles = roles; + return NGX_CONF_OK; - scheme = (r->connection->ssl) ? "https" : "http"; - server = r->headers_in.server; +error: + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "an error occurred creating the policy"); - // get the URI - uri_variable_hash = ngx_hash_key(uri_variable_name.data, uri_variable_name.len); - request_uri_var = ngx_http_get_variable(r, &uri_variable_name, uri_variable_hash); + if (users != NULL) + { + ngx_array_destroy(users); + } - // get the URI - if(request_uri_var && !request_uri_var->not_found && request_uri_var->valid) - { - // ideally we would like the uri with the querystring parameters - uri.data = ngx_palloc(r->pool, request_uri_var->len); - uri.len = request_uri_var->len; - ngx_memcpy(uri.data, request_uri_var->data, request_uri_var->len); + if (roles != NULL) + { + ngx_array_destroy(roles); + } - // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "found uri with querystring %s", ngx_str_t_to_char_ptr(r->pool, uri)); - } - else - { - // fallback to the querystring without params - uri = r->uri; + return NGX_CONF_ERROR; +} - // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "fallback to querystring without params"); - } +static char *ngx_http_auth_jwt_add_grant_header_mapping(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_str_t *grant; + ngx_str_t *header; + ngx_str_t *replace; + ngx_array_t **mappings; + ngx_http_auth_jwt_grant_mapping_t *mapping; - // escape the URI - escaped_len = 2 * ngx_escape_uri(NULL, uri.data, uri.len, NGX_ESCAPE_ARGS) + uri.len; - uri_escaped.data = ngx_palloc(r->pool, escaped_len); - uri_escaped.len = escaped_len; - ngx_escape_uri(uri_escaped.data, uri.data, uri.len, NGX_ESCAPE_ARGS); - - r->headers_out.location->value.len = loginlen + sizeof("?return_url=") - 1 + strlen(scheme) + sizeof("://") - 1 + server.len + uri_escaped.len; - return_url = ngx_palloc(r->pool, r->headers_out.location->value.len); - ngx_memcpy(return_url, jwtcf->auth_jwt_loginurl.data, jwtcf->auth_jwt_loginurl.len); - int return_url_idx = jwtcf->auth_jwt_loginurl.len; - ngx_memcpy(return_url+return_url_idx, "?return_url=", sizeof("?return_url=") - 1); - return_url_idx += sizeof("?return_url=") - 1; - ngx_memcpy(return_url+return_url_idx, scheme, strlen(scheme)); - return_url_idx += strlen(scheme); - ngx_memcpy(return_url+return_url_idx, "://", sizeof("://") - 1); - return_url_idx += sizeof("://") - 1; - ngx_memcpy(return_url+return_url_idx, server.data, server.len); - return_url_idx += server.len; - ngx_memcpy(return_url+return_url_idx, uri_escaped.data, uri_escaped.len); - return_url_idx += uri_escaped.len; - r->headers_out.location->value.data = (u_char *)return_url; - - // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "return_url: %s", ngx_str_t_to_char_ptr(r->pool, r->headers_out.location->value)); + mappings = (ngx_array_t **)((char *)conf + cmd->offset); + + if (*mappings == NGX_CONF_UNSET_PTR) + { + *mappings = ngx_array_create(cf->pool, 1, sizeof(ngx_http_auth_jwt_policy_t)); + if (*mappings == NULL) + { + return NGX_CONF_ERROR; } - else + } + + grant = &((ngx_str_t *)cf->args->elts)[1]; + header = &((ngx_str_t *)cf->args->elts)[2]; + if (grant->len > 0 && header->len > 0) + { + mapping = (ngx_http_auth_jwt_grant_mapping_t *)ngx_array_push(*mappings); + if (mapping == NULL) { - // for non-get requests, redirect to the login page without a return URL - r->headers_out.location->value.len = jwtcf->auth_jwt_loginurl.len; - r->headers_out.location->value.data = jwtcf->auth_jwt_loginurl.data; + return NGX_CONF_ERROR; } - if (jwtcf->auth_jwt_redirect) + mapping->grant = *grant; + mapping->header = *header; + + if (cf->args->nelts == 4) { - return NGX_HTTP_MOVED_TEMPORARILY; + replace = &((ngx_str_t *)cf->args->elts)[3]; + if (replace->len == 0) + { + mapping->replace = 1; + } + else if (ngx_strcasecmp(replace->data, (u_char *)"on") == 0) + { + mapping->replace = 1; + } + else if (ngx_strcasecmp(replace->data, (u_char *)"off") == 0) + { + mapping->replace = 0; + } + else + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid value \"%s\" in \"%s\" directive, " + "it must be \"on\" or \"off\"", + replace->data, cmd->name.data); + return NGX_CONF_ERROR; + } } else { - return NGX_HTTP_UNAUTHORIZED; + mapping->replace = 1; } -} + ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "found valid mapping for grant (%s) and header (%s)", grant->data, header->data); + } -static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf) -{ - ngx_http_handler_pt *h; - ngx_http_core_main_conf_t *cmcf; + return NGX_CONF_OK; +} - cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); +static char *get_jwt_from_header(ngx_http_request_t *r, ngx_str_t *context) +{ + ngx_table_elt_t *authorization_header; + ngx_str_t jwt_http_value; - h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); - if (h == NULL) + // using authorization header + authorization_header = search_headers_in(r, context); + if (authorization_header != NULL && authorization_header->value.len > (sizeof("Bearer ") - 1)) { - return NGX_ERROR; - } + jwt_http_value.data = authorization_header->value.data + (sizeof("Bearer ") - 1); + jwt_http_value.len = authorization_header->value.len - (sizeof("Bearer ") - 1); - *h = ngx_http_auth_jwt_handler; + return ngx_str_t_to_char_ptr(r->pool, jwt_http_value); + } - return NGX_OK; + return NULL; } - -static void * -ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf) +static char *get_jwt_from_cookie(ngx_http_request_t *r, ngx_str_t *context) { - ngx_http_auth_jwt_loc_conf_t *conf; + ngx_str_t jwt_http_value; + ngx_table_elt_t* cookie; - conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_jwt_loc_conf_t)); - if (conf == NULL) + // get the cookie + cookie = ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, context, &jwt_http_value); + if (cookie == NULL) { return NULL; } - - // set the flag to unset - conf->auth_jwt_enabled = (ngx_flag_t) -1; - conf->auth_jwt_redirect = (ngx_flag_t) -1; - conf->auth_jwt_validate_email = (ngx_flag_t) -1; - conf->auth_jwt_use_keyfile = (ngx_flag_t) -1; - ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "Created Location Configuration"); - - return conf; + return ngx_str_t_to_char_ptr(r->pool, jwt_http_value); } - -static char * -ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +static char *get_jwt_from_url(ngx_http_request_t *r, ngx_str_t *context) { - ngx_http_auth_jwt_loc_conf_t *prev = parent; - ngx_http_auth_jwt_loc_conf_t *conf = child; + u_char *p, *equal, *amp, *last; - ngx_conf_merge_str_value(conf->auth_jwt_loginurl, prev->auth_jwt_loginurl, ""); - ngx_conf_merge_str_value(conf->auth_jwt_key, prev->auth_jwt_key, ""); - ngx_conf_merge_str_value(conf->auth_jwt_validation_type, prev->auth_jwt_validation_type, ""); - ngx_conf_merge_str_value(conf->auth_jwt_algorithm, prev->auth_jwt_algorithm, "HS256"); - ngx_conf_merge_str_value(conf->auth_jwt_keyfile_path, prev->auth_jwt_keyfile_path, KEY_FILE_PATH); - ngx_conf_merge_off_value(conf->auth_jwt_validate_email, prev->auth_jwt_validate_email, 1); - - if (conf->auth_jwt_enabled == ((ngx_flag_t) -1)) + if (r->args.len > context->len + 1) { - conf->auth_jwt_enabled = (prev->auth_jwt_enabled == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_enabled; + p = (u_char *)ngx_strstr(r->args.data, context->data); + if (p != NULL) + { + last = r->args.data + r->args.len; + equal = ngx_strlchr(p + context->len, last, '='); + if (equal != NULL) + { + amp = ngx_strlchr(++equal, last, '&'); + if (amp == NULL) + { + amp = last; + } + + if (amp - equal > 0) + { + return ngx_uchar_to_char_ptr(r->pool, equal, amp - equal); + } + } + } } - if (conf->auth_jwt_redirect == ((ngx_flag_t) -1)) + return NULL; +} + +static ngx_flag_t matches_jwt_policy(ngx_http_request_t *r, const char *user, const char *role, ngx_array_t *policies) +{ + size_t i; + ngx_http_auth_jwt_policy_t *policy; + + if (role == NULL) { - conf->auth_jwt_redirect = (prev->auth_jwt_redirect == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_redirect; - } +#if NGX_DEBUG + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "validating policies against and role=%s", role); +#endif - if (conf->auth_jwt_use_keyfile == ((ngx_flag_t) -1)) + for (i = 0; i < policies->nelts; i++) + { + policy = &((ngx_http_auth_jwt_policy_t *)policies->elts)[i]; + +#if NGX_DEBUG + ngx_log_policy(NGX_LOG_INFO, r->connection->log, 0, policy); +#endif + + if (policy->users->nelts > 0) + { + if (user == NULL || ngx_array_includes_insensitive(policy->users, user) == 0) + { + return 0; + } + } + + if (policy->roles->nelts == 0) + { +#if NGX_DEBUG + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "policy validation successful"); +#endif + + return 1; + } + } + } + else { - conf->auth_jwt_use_keyfile = (prev->auth_jwt_use_keyfile == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_use_keyfile; +#if NGX_DEBUG + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "validating policies against user=%s and role=%s", user, role); +#endif + + for (i = 0; i < policies->nelts; i++) + { + policy = &((ngx_http_auth_jwt_policy_t *)policies->elts)[i]; + +#if NGX_DEBUG + ngx_log_policy(NGX_LOG_INFO, r->connection->log, 0, policy); +#endif + + if (policy->users->nelts > 0) + { + if (user == NULL || ngx_array_includes_insensitive(policy->users, user) == 0) + { + return 0; + } + } + + if (policy->roles->nelts == 1) + { +#if NGX_DEBUG + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "checking role '%s'", ((ngx_str_t *)policy->roles->elts)[0].data); +#endif + + if (ngx_strcasecmp(((ngx_str_t *)policy->roles->elts)[0].data, (u_char *)role) == 0) + { +#if NGX_DEBUG + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "role '%s' found", ((ngx_str_t *)policy->roles->elts)[0].data); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "policy validation successful"); +#endif + + return 1; + } + +#if NGX_DEBUG + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "role '%s' not found", ((ngx_str_t *)policy->roles->elts)[0].data); +#endif + } + } } - return NGX_CONF_OK; + return 0; } -static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type) +static ngx_flag_t matches_jwt_policy_n(ngx_http_request_t *r, const char *user, json_t *json_roles, size_t json_roles_count, ngx_array_t *policies) { - static const ngx_str_t authorizationHeaderName = ngx_string("Authorization"); - ngx_table_elt_t *authorizationHeader; - char* jwtCookieValChrPtr = NULL; - ngx_str_t jwtCookieVal; - ngx_int_t n; - ngx_str_t authorizationHeaderStr; + ngx_flag_t result; + size_t i, j; + const char *role; + ngx_http_auth_jwt_policy_t *policy; - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "auth_jwt_validation_type.len %d", auth_jwt_validation_type.len); + hashset_t hashset; + hashset.nentries = 0; + hashset.capacity = json_roles_count; - if (auth_jwt_validation_type.len == 0 || (auth_jwt_validation_type.len == sizeof("AUTHORIZATION") - 1 && ngx_strncmp(auth_jwt_validation_type.data, "AUTHORIZATION", sizeof("AUTHORIZATION") - 1)==0)) + if (json_roles_count > 1024) + { + hashset.buckets = (size_t *)ngx_palloc(r->pool, json_roles_count * sizeof(size_t)); + hashset.entries = (hashset_entry_t *)ngx_palloc(r->pool, json_roles_count * sizeof(hashset_entry_t)); + } + else { - // using authorization header - authorizationHeader = search_headers_in(r, authorizationHeaderName.data, authorizationHeaderName.len); - if (authorizationHeader != NULL) + hashset.buckets = (size_t *)alloca(json_roles_count * sizeof(size_t)); + hashset.entries = (hashset_entry_t *)alloca(json_roles_count * sizeof(hashset_entry_t)); + } + + ngx_memzero(hashset.buckets, json_roles_count * sizeof(size_t)); + ngx_memzero(hashset.entries, json_roles_count * sizeof(hashset_entry_t)); + + for (i = 0; i < json_roles_count; i++) + { + role = json_string_value(json_array_get(json_roles, i)); + if (role == NULL) + { + continue; + } + +#if NGX_DEBUG + if (user == NULL) { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Found authorization header len %d", authorizationHeader->value.len); + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "validating policies against and role=%s", role); + } + else + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "validating policies against user=%s and role=%s", user, role); + } +#endif - authorizationHeaderStr.data = authorizationHeader->value.data + sizeof("Bearer ") - 1; - authorizationHeaderStr.len = authorizationHeader->value.len - (sizeof("Bearer ") - 1); + hashset_add(&hashset, role); + } - jwtCookieValChrPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr); + for (i = 0; i < policies->nelts;) + { + policy = &((ngx_http_auth_jwt_policy_t *)policies->elts)[i]; + +#if NGX_DEBUG + ngx_log_policy(NGX_LOG_INFO, r->connection->log, 0, policy); +#endif + + if (policy->users->nelts > 0) + { + if (user == NULL || ngx_array_includes_insensitive(policy->users, user) == 0) + { + goto next_policy; + } + } - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtCookieValChrPtr); + if (policy->roles->nelts > json_roles_count) + { + goto next_policy; } + + for (j = 0; j < policy->roles->nelts;) + { +#if NGX_DEBUG + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "checking role '%s'", ((ngx_str_t *)policy->roles->elts)[j].data); +#endif + + if (hashset_contains(&hashset, (const char *)((ngx_str_t *)policy->roles->elts)[j].data)) + { +#if NGX_DEBUG + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "role '%s' found", ((ngx_str_t *)policy->roles->elts)[j].data); +#endif + + goto next_role; + } + +#if NGX_DEBUG + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "role '%s' not found", ((ngx_str_t *)policy->roles->elts)[j].data); +#endif + + goto next_policy; + + next_role: + j++; + } + +#if NGX_DEBUG + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "policy validation successful"); +#endif + + result = 1; + goto exit; + + next_policy: + i++; } - else if (auth_jwt_validation_type.len > sizeof("COOKIE=") && ngx_strncmp(auth_jwt_validation_type.data, "COOKIE=", sizeof("COOKIE=") - 1)==0) + + result = 0; + +exit: + if (json_roles_count > 1024) + { + ngx_pfree(r->pool, hashset.buckets); + ngx_pfree(r->pool, hashset.entries); + } + + return result; +} + +static ngx_flag_t validate_jwt_token_policies(ngx_http_request_t *r, jwt_t *jwt, ngx_http_auth_jwt_loc_conf_t *jwt_cf) +{ + const char *user; + const char *token_roles; + json_t *json_roles; + json_error_t error; + size_t json_roles_count; + + if (jwt_cf->policies == NULL || jwt_cf->policies->nelts == 0) { - auth_jwt_validation_type.data += sizeof("COOKIE=") - 1; - auth_jwt_validation_type.len -= sizeof("COOKIE=") - 1; + return 1; + } - // get the cookie - // TODO: the cookie name could be passed in dynamicallly - n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &auth_jwt_validation_type, &jwtCookieVal); - if (n != NGX_DECLINED) + user = jwt_get_grant(jwt, (const char *)jwt_cf->name_grant.data); + token_roles = jwt_get_grants_json(jwt, (const char *)jwt_cf->role_grant.data); + + json_roles = json_loads(token_roles, JSON_DECODE_ANY | JSON_DISABLE_EOF_CHECK, &error); + if (json_roles != NULL) + { + if (json_is_array(json_roles)) { - jwtCookieValChrPtr = ngx_str_t_to_char_ptr(r->pool, jwtCookieVal); + json_roles_count = json_array_size(json_roles); + if (json_roles_count == 1) + { + if (matches_jwt_policy(r, user, json_string_value(json_array_get(json_roles, 0)), jwt_cf->policies)) + { + goto success; + } + } + else if (json_roles_count > 0) + { + if (matches_jwt_policy_n(r, user, json_roles, json_roles_count, jwt_cf->policies)) + { + goto success; + } + } } + else if (json_is_string(json_roles)) + { + if (matches_jwt_policy(r, user, json_string_value(json_roles), jwt_cf->policies)) + { + goto success; + } + } + + json_decref(json_roles); + } + else + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "an error occurred validating authorization policies: %s", error.text); } - return jwtCookieValChrPtr; + return 0; + +success: + json_decref(json_roles); + return 1; } +static ngx_int_t ngx_http_auth_set_headers(ngx_http_request_t *r, jwt_t *jwt, ngx_http_auth_jwt_loc_conf_t *jwt_cf) +{ + const char *grant; + ngx_str_t grant_t; + size_t i; + ngx_http_auth_jwt_grant_mapping_t *mapping; + if (jwt_cf->grant_header_mappings == NULL || jwt_cf->grant_header_mappings->nelts == 0) + { + return 1; + } + for (i = 0; i < jwt_cf->grant_header_mappings->nelts; i++) + { + mapping = &((ngx_http_auth_jwt_grant_mapping_t *)jwt_cf->grant_header_mappings->elts)[i]; + + grant = jwt_get_grant(jwt, (const char *)mapping->grant.data); + if (grant == NULL) + { + continue; + } + + grant_t = ngx_char_ptr_to_str_t(r->pool, (char *)grant); + if (grant_t.data == NULL) + { + return 0; + } + + if (set_custom_header_in_headers_out(r, &mapping->header, &grant_t) != NGX_OK) + { + return 0; + } + } + return 1; +} \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_string.c b/src/ngx_http_auth_jwt_string.c index 186121f..e3816c4 100644 --- a/src/ngx_http_auth_jwt_string.c +++ b/src/ngx_http_auth_jwt_string.c @@ -11,22 +11,196 @@ #include "ngx_http_auth_jwt_string.h" /** copies an nginx string structure to a newly allocated character pointer */ -char* ngx_str_t_to_char_ptr(ngx_pool_t *pool, ngx_str_t str) +char *ngx_str_t_to_char_ptr(ngx_pool_t *pool, ngx_str_t str) { - char* char_ptr = ngx_palloc(pool, str.len + 1); + char *char_ptr = ngx_palloc(pool, str.len + 1); ngx_memcpy(char_ptr, str.data, str.len); *(char_ptr + str.len) = '\0'; return char_ptr; } +char *ngx_uchar_to_char_ptr(ngx_pool_t *pool, u_char *str, size_t len) +{ + char *char_ptr = ngx_palloc(pool, len + 1); + ngx_memcpy(char_ptr, str, len); + *(char_ptr + len) = '\0'; + return char_ptr; +} + /** copies a character pointer string to an nginx string structure */ -ngx_str_t ngx_char_ptr_to_str_t(ngx_pool_t *pool, char* char_ptr) +ngx_str_t ngx_char_ptr_to_str_t(ngx_pool_t *pool, char *char_ptr) { - int len = strlen(char_ptr); + size_t len = ngx_strlen(char_ptr); ngx_str_t str_t; str_t.data = ngx_palloc(pool, len); ngx_memcpy(str_t.data, char_ptr, len); str_t.len = len; return str_t; +} + +ngx_flag_t ngx_array_includes(ngx_array_t *array, const char *value) +{ + size_t i; + ngx_str_t *entry; + + for (i = 0; i < array->nelts; i++) + { + entry = &((ngx_str_t *)array->elts)[i]; + + if (ngx_strcmp((u_char *)entry->data, (u_char *)value) == 0) + { + return 1; + } + } + + return 0; +} + +ngx_flag_t ngx_array_includes_insensitive(ngx_array_t *array, const char *value) +{ + size_t i; + ngx_str_t *entry; + + for (i = 0; i < array->nelts; i++) + { + entry = &((ngx_str_t *)array->elts)[i]; + + if (ngx_strcasecmp((u_char *)entry->data, (u_char *)value) == 0) + { + return 1; + } + } + + return 0; +} + +size_t trim(char **value, char *next) +{ + size_t len; + char *start; + char *end; + + start = *value; + if (next == NULL) + { + len = strlen(start); + } + else + { + len = next - start; + } + + if (len == 0) + { + return 0; + } + + while (isspace(*start)) + { + start++; + len--; + } + + end = start + len; + while (end > start && isspace(*end)) + { + end--; + len--; + } + + end[1] = '\0'; + *value = start; + return len; +} + +ngx_int_t ngx_str_split(ngx_str_t *value, ngx_array_t *result, const char *separator) +{ + size_t len; + ngx_str_t *entry; + char *nextToken; + char *token; + size_t separatorLen = strlen(separator); + + if (value->len > 0) + { + if (separatorLen == 0) + { + entry = (ngx_str_t *)ngx_array_push(result); + if (entry == NULL) + { + return NGX_ERROR; + } + + entry->data = value->data; + entry->len = value->len; + } + else + { + token = (char *)value->data; + + do + { + nextToken = ngx_strstr(token, separator); + len = trim(&token, nextToken); + + if (len > 0) + { + entry = (ngx_str_t *)ngx_array_push(result); + if (entry == NULL) + { + return NGX_ERROR; + } + + token[len] = '\0'; + entry->len = len; + entry->data = (u_char *)token; + } + + token = nextToken + separatorLen; + } while (nextToken != NULL); + } + } + + return NGX_OK; +} + +ngx_int_t ngx_str_join(const ngx_array_t *value, ngx_str_t *result, const char *separator) +{ + size_t i; + ngx_str_t *element; + size_t offset; + size_t separatorLen; + + if (result->len > 0) + { + offset = 0; + separatorLen = ngx_strlen(separator); + + for (i = 0; i < value->nelts; i++) + { + element = &((ngx_str_t *)value->elts)[i]; + if (offset + element->len + separatorLen >= result->len) + { + break; + } + + ngx_memcpy(result->data + offset, element->data, element->len); + offset += element->len; + + ngx_memcpy(result->data + offset, separator, separatorLen); + offset += separatorLen; + } + + if (offset > separatorLen) + { + result->data[offset - separatorLen] = '\0'; + } + else + { + result->data[0] = '\0'; + } + } + + return NGX_OK; } \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_string.h b/src/ngx_http_auth_jwt_string.h index 594785b..1989477 100644 --- a/src/ngx_http_auth_jwt_string.h +++ b/src/ngx_http_auth_jwt_string.h @@ -12,7 +12,12 @@ #include -char* ngx_str_t_to_char_ptr(ngx_pool_t *pool, ngx_str_t str); -ngx_str_t ngx_char_ptr_to_str_t(ngx_pool_t *pool, char* char_ptr); +char *ngx_str_t_to_char_ptr(ngx_pool_t *pool, ngx_str_t str); +ngx_str_t ngx_char_ptr_to_str_t(ngx_pool_t *pool, char *char_ptr); +char *ngx_uchar_to_char_ptr(ngx_pool_t *pool, u_char *str, size_t len); +ngx_flag_t ngx_array_includes(ngx_array_t *array, const char *value); +ngx_flag_t ngx_array_includes_insensitive(ngx_array_t *array, const char *value); +ngx_int_t ngx_str_split(ngx_str_t *value, ngx_array_t *result, const char *separator); +ngx_int_t ngx_str_join(const ngx_array_t *value, ngx_str_t *result, const char *separator); #endif /* _NGX_HTTP_AUTH_JWT_STRING_H */ \ No newline at end of file