Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/vendor/
.DS_Store
.phpunit.result.cache

30 changes: 28 additions & 2 deletions src/IndieAuth/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,37 @@ private static function _isIssuerValid($issuer, $metadata_endpoint) {

$metadata_endpoint = self::normalizeMeURL($metadata_endpoint);

if (strpos($metadata_endpoint, $issuer) !== 0) {
// The issuer is a prefix of the metadata URL (IndieAuth), or the metadata
// URL is the RFC 8414 well-known location for the issuer, which inserts
// /.well-known/oauth-authorization-server between the host and the
// issuer's path. The next IndieAuth draft uses the RFC 8414 form; servers
// such as IndieKey.id already do.
if (strpos($metadata_endpoint, $issuer) === 0) {
return true;
}

return rtrim($metadata_endpoint, '/') === self::wellKnownMetadataURL($issuer);
}

/**
* The RFC 8414 (section 3) metadata location for an issuer identifier:
* https://host/.well-known/oauth-authorization-server followed by the
* issuer's path. Without a trailing slash, for comparison. False when the
* issuer is not a URL.
*
* @param string $issuer a normalized issuer URL
* @return string|false
*/
public static function wellKnownMetadataURL($issuer) {
$parts = parse_url($issuer);
if(!is_array($parts) || !isset($parts['scheme'], $parts['host'])) {
return false;
}

return true;
$base = strtolower($parts['scheme']) . '://' . strtolower($parts['host']) . (isset($parts['port']) ? ':' . $parts['port'] : '');
$path = rtrim($parts['path'] ?? '', '/');

return $base . '/.well-known/oauth-authorization-server' . $path;
}

private static function _fetchHead($url) {
Expand Down
76 changes: 76 additions & 0 deletions tests/MetadataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,66 @@ public function testIsIssuerValid() {
);

$this->assertTrue($result);

# RFC 8414: the well-known path is inserted between the host and the issuer's path
$result = $this->_invokeStaticMethod(
Client::class,
'_isIssuerValid',
['https://example.com/s/abc/', 'https://example.com/.well-known/oauth-authorization-server/s/abc']
);

$this->assertTrue($result);

# OpenID Connect discovery has its own rules and is not IndieAuth metadata
$result = $this->_invokeStaticMethod(
Client::class,
'_isIssuerValid',
['https://example.com/s/abc/', 'https://example.com/.well-known/openid-configuration/s/abc']
);

$this->assertFalse($result);

# ... and for an issuer without a path (also a prefix match)
$result = $this->_invokeStaticMethod(
Client::class,
'_isIssuerValid',
['https://example.com/', 'https://example.com/.well-known/oauth-authorization-server']
);

$this->assertTrue($result);

# the well-known location for a different path is not this issuer's
$result = $this->_invokeStaticMethod(
Client::class,
'_isIssuerValid',
['https://example.com/s/abc/', 'https://example.com/.well-known/oauth-authorization-server/s/xyz']
);

$this->assertFalse($result);

# nor is the well-known location on another host
$result = $this->_invokeStaticMethod(
Client::class,
'_isIssuerValid',
['https://example.com/s/abc/', 'https://example.org/.well-known/oauth-authorization-server/s/abc']
);

$this->assertFalse($result);

# http is still refused, well-known or not
$result = $this->_invokeStaticMethod(
Client::class,
'_isIssuerValid',
['http://example.com/s/abc/', 'http://example.com/.well-known/oauth-authorization-server/s/abc']
);

$this->assertFalse($result);
}

public function testWellKnownMetadataURL() {
$this->assertEquals('https://example.com/.well-known/oauth-authorization-server/s/abc', Client::wellKnownMetadataURL('https://example.com/s/abc/'));
$this->assertEquals('https://example.com:8443/.well-known/oauth-authorization-server', Client::wellKnownMetadataURL('https://example.com:8443/'));
$this->assertFalse(Client::wellKnownMetadataURL('not a url'));
}

public function testDiscoverIssuer()
Expand Down Expand Up @@ -175,6 +235,22 @@ public function testDiscoverIssuerNotAPrefix()
$this->assertInstanceOf(ErrorResponse::class, $result);
}

/**
* `issuer` may also name the RFC 8414 well-known location the metadata was fetched from
*/
public function testDiscoverIssuerAtWellKnownLocation()
{
$metadata_endpoint = 'https://example.com/.well-known/oauth-authorization-server/s/abc';
Client::setMetadata($metadata_endpoint, '{"issuer":"https://example.com/s/abc/","authorization_endpoint":"https://example.com/s/abc/auth","token_endpoint":"https://example.com/token"}');

$result = $this->_invokeStaticMethod(
Client::class,
'discoverIssuer',
[$metadata_endpoint]
);
$this->assertEquals('https://example.com/s/abc/', $result);
}

public function testDiscoverRevocationEndpoint() {
Client::setMetadata('https://example.com/', $this->default_metadata);

Expand Down
Loading