Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
joecorall committed Jan 31, 2025
0 parents commit 43da773
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 0 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/lint-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: lint-test

on:
push:
branches:
- '**'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint-test:
name: lint+test
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ["8.1", "8.2", "8.3"]
drupal-version: ["10.3", "10.4", "11.0"]
exclude:
- drupal-version: "11.0"
php-version: "8.1"
- drupal-version: "11.0"
php-version: "8.2"
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: lint+test
working-directory: tests
run: |
export MODULE_DIRECTORY=$(pwd | xargs dirname)
docker compose up --quiet-pull --abort-on-container-exit
env:
DRUPAL_VERSION: ${{ matrix.drupal-version }}
PHP_VERSION: ${{ matrix.php-version }}
ENABLE_MODULES: islandora_jwks
27 changes: 27 additions & 0 deletions .github/workflows/mirror.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Mirror to drupal.org
on:
push:
branches:
- 1.x
tags:
- '*'

jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Mirror + trigger CI
uses: SvanBoxel/gitlab-mirror-and-ci-action@master
with:
args: "https://git.drupalcode.org/project/islandora_jwks"
env:
FOLLOW_TAGS: "true"
FORCE_PUSH: "false"
GITLAB_HOSTNAME: "git.drupal.org"
GITLAB_USERNAME: "foo"
GITLAB_PASSWORD: ${{ secrets.GITLAB_PASSWORD }}
GITLAB_PROJECT_ID: "173125"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 changes: 28 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Create release
on:
pull_request_target:
branches:
- 1.x
types:
- closed
permissions:
contents: write
actions: write
jobs:
release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: install autotag binary
run: curl -sL https://git.io/autotag-install | sudo sh -s -- -b /usr/bin

- name: create release
run: |-
TAG=$(autotag)
git tag $TAG
git push origin $TAG
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## INTRODUCTION

The Islandora JWKS module provides a JWKS URI for Islandora's JWT tokens at `/oauth/discovery/keys`.

## REQUIREMENTS

- drupal/jwt
- drupal/islandora

## INSTALLATION

Install as you would normally install a contributed Drupal module.
See: https://www.drupal.org/node/895232 for further information.

## CONFIGURATION
- Ensure your JWT public key is available at `/var/run/s6/container_environment/JWT_PUBLIC_KEY`. If using [isle-buildkit](https://github.com/islandora-devops/isle-buildkit) to run your Islandora site this will be handled automatically for you.

## MAINTAINERS

Current maintainers for Drupal 10:

- Joe Corall (joecorall) - https://www.drupal.org/u/joecorall
20 changes: 20 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "drupal/islandora_jwks",
"description": "Provide a JWKS URI for Islandora's JWT tokens",
"type": "drupal-module",
"license": "GPL-2.0+",
"homepage": "https://www.drupal.org/project/islandora_jwks",
"support": {
"issues": "https://www.drupal.org/project/issues/islandora_jwks"
},
"authors": [
{
"name": "Joe Corall",
"email": "[email protected]",
"role": "Owner"
}
],
"require" : {
"drupal/islandora": "^2.13"
}
}
8 changes: 8 additions & 0 deletions islandora_jwks.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: 'Islandora JWKS'
type: module
description: 'Provide a JWKS URI for Islandora's JWT tokens'
package: Islandora
core_version_requirement: ^10 || ^11
dependencies:
- islandora:islandora
- jwt:jwt
6 changes: 6 additions & 0 deletions islandora_jwks.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

/**
* @file
* Primary module hooks for Islandora JWKS module.
*/
7 changes: 7 additions & 0 deletions islandora_jwks.routing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
islandora_jwks.uri:
path: '/oauth/discovery/keys'
defaults:
_title: 'Keys'
_controller: '\Drupal\islandora_jwks\Controller\IslandoraJwksController'
requirements:
_permission: 'access content'
66 changes: 66 additions & 0 deletions src/Controller/IslandoraJwksController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Drupal\islandora_jwks\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

/**
* Returns responses for Islandora JWKS routes.
*/
class IslandoraJwksController extends ControllerBase {

/**
* Builds the JWKS response.
*/
public function __invoke(): JsonResponse {
$keyPath = '/var/run/s6/container_environment/JWT_PUBLIC_KEY';

$publicKey = $this->readPublicKey($keyPath);
if ($publicKey === NULL) {
return new JsonResponse(['error' => 'Public key not found'], Response::HTTP_INTERNAL_SERVER_ERROR);
}

$opensslKey = openssl_pkey_get_public($publicKey);
if ($opensslKey === FALSE) {
return new JsonResponse(['error' => 'Invalid RSA public key'], Response::HTTP_INTERNAL_SERVER_ERROR);
}

$details = openssl_pkey_get_details($opensslKey);
if (!$details || !isset($details['rsa'])) {
return new JsonResponse(['error' => 'Failed to extract RSA key details'], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$jwks = [
'keys' => [
[
'kty' => 'RSA',
'kid' => sha1($publicKey),
'use' => 'sig',
'alg' => 'RS256',
'n' => $this->base64UrlEncode($details['rsa']['n']),
'e' => $this->base64UrlEncode($details['rsa']['e']),
],
],
];

return new JsonResponse($jwks, Response::HTTP_OK);
}

/**
* Reads the public key from disk.
*/
protected function readPublicKey(string $path): ?string {
return is_readable($path) ? trim(file_get_contents($path)) : NULL;
}

/**
* Encodes data in base64 URL format.
*/
private function base64UrlEncode(string $data): string {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

}
22 changes: 22 additions & 0 deletions tests/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
networks:
default:
services:
chromedriver:
image: drupalci/webdriver-chromedriver:production
entrypoint:
- chromedriver
- "--log-path=/dev/null"
- "--verbose"
- "--allowed-ips="
- "--allowed-origins=*"
drupal:
image: lehighlts/drupal-ci:${DRUPAL_VERSION}-php${PHP_VERSION}
volumes:
- ${MODULE_DIRECTORY}:/var/www/drupal/web/modules/contrib/${ENABLE_MODULES}
environment:
SIMPLETEST_BASE_URL: http://drupal:8282
ENABLE_MODULES: ${ENABLE_MODULES}
MINK_DRIVER_ARGS_WEBDRIVER: '["chrome", {"browserName":"chrome","goog:chromeOptions":{"args":["--disable-gpu","--headless", "--no-sandbox", "--disable-dev-shm-usage"]}}, "http://chromedriver:9515"]'
SYMFONY_DEPRECATIONS_HELPER: weak
links:
- chromedriver
57 changes: 57 additions & 0 deletions tests/src/Unit/Controller/IslandoraJwksControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Drupal\Tests\islandora_jwks\Unit\Controller;

use Drupal\islandora_jwks\Controller\IslandoraJwksController;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\JsonResponse;

/**
* Tests the IslandoraJwksController.
*
* @group islandora_jwks
*/
final class IslandoraJwksControllerTest extends TestCase {

/**
* Tests the JWKS response.
*/
public function testJwksResponse(): void {
// Sample RSA public key (PEM format)
$publicKey = <<<EOD
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6uK3nozywVaRCAB3FHdR
ZNHunSZvN/c31QimZAqQMGxj7JrGh1LF8JRX+XAQ+CJcPD9r6xXjKSS1Gqa2Os2w
ARr/9abIwG5QeNsrJ8GMt3Z/WICnNeaFAkUVviwKWcA61iFJWvTDAuI0hCaxArRK
sk0BfFSMh+4u3JAdD9tUxUx6AAUXUCdtPyluaBd53wuB0r9xRlPnDw6I9QHfKK80
Xrrsu1PYATgrsy69stzCln3KlO5Oxc6O8OjMdjC2D2c3HmsO4CKPvvaVuaow/a9P
a3SNje4UXN+/1xUfQskxafP8CKVSr8xxtwzSureiskb5/98moAiutpUtp15yyAm0
rwIDAQAB
-----END PUBLIC KEY-----
EOD;

$mock = $this->getMockBuilder(IslandoraJwksController::class)
->onlyMethods(['readPublicKey'])
->getMock();

$mock->method('readPublicKey')
->willReturn($publicKey);

$response = $mock->__invoke();

$this->assertInstanceOf(JsonResponse::class, $response);

$jwks = json_decode($response->getContent(), TRUE);
$this->assertArrayHasKey('keys', $jwks);
$this->assertCount(1, $jwks['keys']);
$this->assertEquals('RSA', $jwks['keys'][0]['kty']);
$this->assertEquals('RS256', $jwks['keys'][0]['alg']);
$this->assertEquals('sig', $jwks['keys'][0]['use']);
$this->assertEquals('df52de600a824a30ba84a4745b0ebcebf7edca44', $jwks['keys'][0]['kid']);
$this->assertArrayHasKey('n', $jwks['keys'][0]);
$this->assertArrayHasKey('e', $jwks['keys'][0]);
}

}

0 comments on commit 43da773

Please sign in to comment.