Skip to content

Commit

Permalink
Merge pull request #51 from MO-RISE/feat/issue-25
Browse files Browse the repository at this point in the history
Fixing issue 25: Configurable required and allowed claims in token generator
  • Loading branch information
freol35241 authored Aug 30, 2023
2 parents d2a15b7 + f5d9649 commit 062e27d
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 41 deletions.
4 changes: 3 additions & 1 deletion auth/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ EXPOSE 8080
ENV JWT_ISSUER=
ENV JWT_EXPIRY=
ENV JWT_SECRET=
ENV JWT_ALLOWED_CLAIMS=
ENV JWT_REQUIRED_CLAIMS=

# Install neccessary tools
RUN apt-get update && apt-get install -y \
Expand All @@ -19,4 +21,4 @@ RUN wget -q -O - https://github.com/msoap/shell2http/releases/download/v1.16.0/s
RUN wget -q -O - https://github.com/mike-engel/jwt-cli/releases/download/5.0.3/jwt-linux.tar.gz | tar xvz -C /usr/local/bin jwt

ADD --chmod=a+x issue_jwt.sh issue_jwt.sh
CMD ["shell2http", "--500", "--form", "--export-all-vars", "POST:/", "./issue_jwt.sh"]
CMD ["shell2http", "--cgi", "--show-errors", "--form", "--export-all-vars", "POST:/", "./issue_jwt.sh"]
54 changes: 54 additions & 0 deletions auth/issue_jwt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,63 @@ jsonify_prefixed_variables() {
echo "$json"
}

# Define a function the splits a comma separated string into the substrings
split_comma_separated_string() {
local input="$1" # Input comma-separated string
local IFS="," # Internal Field Separator set to comma

# Loop through the input string and print elements
for item in $input; do
echo "$item"
done
}

# Define a function that tests if array A is a subset of array B
# Returns 0 if A is a subset of B
# Returns 1 if A is NOT a subset of B
subset_of() {
local -n _array_A=$1
local -n _array_B=$2
_file_A=$(printf '%s\n' "${_array_A[@]}")
_file_B=$(printf '%s\n' "${_array_B[@]}")

output=$(comm -23 <(sort <<< "$_file_A") <(sort <<< "$_file_B") | head -1)
if [[ -z $output ]]; then return 0; else return 1; fi
}

## Start processing input

# Extract variables from shell2http input
form_data_json=$(jsonify_prefixed_variables "v_")

# shellcheck disable=SC2034
mapfile -t json_keys < <(echo "$form_data_json" | jq -r 'keys[]')

# Read allowed claims from environment variable and
# verify form_data_json using allowed_claims.
# Return error if verification fails!
if [ -n "$JWT_ALLOWED_CLAIMS" ]; then
# shellcheck disable=SC2034
mapfile -t allowed_claims < <(split_comma_separated_string "$JWT_ALLOWED_CLAIMS")
if ! subset_of json_keys allowed_claims; then
printf "%s\n\n%s\n" "Status: 400" "You provided: ${json_keys[*]} which is not a subset of the allowed ones: ${allowed_claims[*]}"
exit 1
fi
fi

# Read required claims from environment variable and
# verify form_data_json using required_claims.
# Return error if verification fails!
if [ -n "$JWT_REQUIRED_CLAIMS" ]; then
# shellcheck disable=SC2034
mapfile -t required_claims < <(split_comma_separated_string "$JWT_REQUIRED_CLAIMS")
if ! subset_of required_claims json_keys; then
printf "%s\n\n%s\n" "Status: 400" "You provided: ${json_keys[*]} which is not a superset of the required ones: ${required_claims[*]}"
exit 1
fi
fi


# Extract variables from environment variables
env_json=$(jsonify_prefixed_variables "JWT_CLAIM_")

Expand Down
85 changes: 85 additions & 0 deletions tests/20-test-issue-jwt.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bats

load "./bats-helpers/bats-support/load"
load "./bats-helpers/bats-assert/load"

# The base setup is started before any test is executed and tore down after the
# last test finishes. In other words, all tests in this file run against the same
# base instance.

setup_file() {
docker pull hivemq/mqtt-cli:4.15.0
docker compose -f docker-compose.base.yml -f docker-compose.auth.yml -f tests/docker-compose.auth-test.yml up -d --build
sleep 10
}

teardown_file() {
docker compose -f docker-compose.base.yml -f docker-compose.auth.yml down --remove-orphans
docker container prune -f && docker volume prune -af
}

@test "AUTH: token generation with ok params" {

run curl -X POST --fail-with-body --location --silent localhost/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "param1=value1&param2=value2&acl=dangerous"

assert_equal "$status" 0

echo "$output"

jwt_as_json=$(echo "$output" | jwt decode -j -)

# For debug
echo "$jwt_as_json"

field=$(echo "$jwt_as_json" | jq .header.typ)
assert_equal "$field" '"JWT"'

field=$(echo "$jwt_as_json" | jq .header.alg)
assert_equal "$field" '"HS256"'

field=$(echo "$jwt_as_json" | jq .payload.iss)
assert_equal "$field" '"pontos-hub"'

field=$(echo "$jwt_as_json" | jq .payload.role)
assert_equal "$field" '"web_user"'

field=$(echo "$jwt_as_json" | jq .payload.param1)
assert_equal "$field" '"value1"'

field=$(echo "$jwt_as_json" | jq .payload.param2)
assert_equal "$field" '"value2"'

# Check that field with the name acl gets overwritten appropriately
field=$(echo "$jwt_as_json" | jq .payload.sub)
assert_equal "$field" '"__token__"'

# Check that field with the name acl gets overwritten appropriately
field=$(echo "$jwt_as_json" | jq .payload.acl)
assert_equal "$field" '""'
}

@test "AUTH: token generation with non-allowed params" {

run curl -X POST --fail-with-body --location localhost/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "param1=value1&not=allowed"

assert_equal "$status" 22
assert_line --partial 'The requested URL returned error: 400'
assert_line --partial 'You provided: not param1 which is not a subset of the allowed ones: param1 param2 param3 acl'

}

@test "AUTH: token generation without required params" {

run curl -X POST --fail-with-body --location localhost/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "param2=value2"

assert_equal "$status" 22
assert_line --partial 'The requested URL returned error: 400'
assert_line --partial 'You provided: param2 which is not a superset of the required ones: param1'

}
40 changes: 0 additions & 40 deletions tests/20-test-auth-setup.bats → tests/21-test-auth-setup.bats
Original file line number Diff line number Diff line change
Expand Up @@ -42,46 +42,6 @@ teardown_file() {
assert_output --partial '200 OK'
}

@test "AUTH: token generation" {

run curl -X POST --location --silent localhost/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "param1=value1&param2=value2&acl=dangerous"

assert_equal "$status" 0

jwt_as_json=$(echo "$output" | jwt decode -j -)

# For debug
echo "$jwt_as_json"

field=$(echo "$jwt_as_json" | jq .header.typ)
assert_equal "$field" '"JWT"'

field=$(echo "$jwt_as_json" | jq .header.alg)
assert_equal "$field" '"HS256"'

field=$(echo "$jwt_as_json" | jq .payload.iss)
assert_equal "$field" '"pontos-hub"'

field=$(echo "$jwt_as_json" | jq .payload.role)
assert_equal "$field" '"web_user"'

field=$(echo "$jwt_as_json" | jq .payload.param1)
assert_equal "$field" '"value1"'

field=$(echo "$jwt_as_json" | jq .payload.param2)
assert_equal "$field" '"value2"'

# Check that field with the name acl gets overwritten appropriately
field=$(echo "$jwt_as_json" | jq .payload.sub)
assert_equal "$field" '"__token__"'

# Check that field with the name acl gets overwritten appropriately
field=$(echo "$jwt_as_json" | jq .payload.acl)
assert_equal "$field" '""'
}

@test "AUTH: REST API access" {

# Should not work as-is
Expand Down
12 changes: 12 additions & 0 deletions tests/docker-compose.auth-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: "3.8"

services:

# JWT issuer
jwt:
build:
context: ./auth
restart: unless-stopped
environment:
- JWT_REQUIRED_CLAIMS=param1
- JWT_ALLOWED_CLAIMS=param1,param2,param3,acl

0 comments on commit 062e27d

Please sign in to comment.