aboutsummaryrefslogtreecommitdiff
path: root/lib/classes
diff options
context:
space:
mode:
Diffstat (limited to 'lib/classes')
-rw-r--r--lib/classes/LtiLink.php16
-rw-r--r--lib/classes/OAuth1.php167
-rw-r--r--lib/classes/auth_plugins/StudipAuthLTI.class.php21
3 files changed, 183 insertions, 21 deletions
diff --git a/lib/classes/LtiLink.php b/lib/classes/LtiLink.php
index 801b6c0..1423cef 100644
--- a/lib/classes/LtiLink.php
+++ b/lib/classes/LtiLink.php
@@ -310,12 +310,14 @@ class LtiLink
// posted form data will always use CR LF
$launch_params = preg_replace("/\r?\n/", "\r\n", $launch_params);
- // In OAuth, request parameters must be sorted by name
- ksort($launch_params);
- $launch_params = http_build_query($launch_params, '', '&', PHP_QUERY_RFC3986);
- $base_string = 'POST&' . rawurlencode($launch_url) . '&' . rawurlencode($launch_params);
- $secret = rawurlencode($this->consumer_secret) . '&';
-
- return base64_encode(hash_hmac($this->oauth_signature_method, $base_string, $secret, true));
+ return Studip\OAuth1::signRequest(
+ (new Slim\Psr7\Factory\ServerRequestFactory())->createServerRequest(
+ 'POST',
+ $launch_url
+ )->withQueryParams($launch_params),
+ $this->consumer_secret,
+ '',
+ $this->oauth_signature_method
+ );
}
}
diff --git a/lib/classes/OAuth1.php b/lib/classes/OAuth1.php
new file mode 100644
index 0000000..1695f9f
--- /dev/null
+++ b/lib/classes/OAuth1.php
@@ -0,0 +1,167 @@
+<?php
+namespace Studip;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use RuntimeException;
+
+/**
+ * Basic oauth1 request handling for Stud.IP using PSR-7 http messages.
+ *
+ * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
+ * @license GPL2 or any later version
+ * @since Stud.IP 6.0
+ */
+final class OAuth1
+{
+ /**
+ * Signs a given request.
+ *
+ * @throws RuntimeException if a request for any other oauth version then
+ * 1.0 shall be signed
+ */
+ public static function signRequest(
+ Request $request,
+ string $consumerSecret,
+ string $tokenSecret,
+ string $method
+ ): string {
+ if (
+ isset($request->getQueryParams()['oauth_version'])
+ && $request->getQueryParams()['oauth_version'] !== '1.0'
+ ) {
+ throw new RuntimeException(self::class . ' only supports OAuth 1.0 requests');
+ }
+
+ return self::hash(
+ $method,
+ self::getSignatureBaseString($request),
+ self::urlencode($consumerSecret) . '&' . self::urlencode($tokenSecret)
+ );
+ }
+
+ /**
+ * Verifies an oauth request.
+ *
+ * @throws RuntimeException if any necessary oauth parameter is missing
+ */
+ public static function verifyRequest(
+ Request $request,
+ string $consumerSecret,
+ string $tokenSecret
+ ): bool {
+ $parameters = self::extractParameters($request);
+
+ $required = [
+ 'oauth_consumer_key',
+ 'oauth_nonce',
+ 'oauth_signature',
+ 'oauth_signature_method',
+ 'oauth_timestamp',
+ ];
+
+ $missing = array_diff($required, array_keys($parameters));
+ if (count($missing) > 0) {
+ throw new RuntimeException('Missing oauth parameters ' . implode(', ', $missing));
+ }
+
+ $signatureToVerify = $parameters['oauth_signature'];
+ unset($parameters['oauth_signature']);
+
+ $signature = self::signRequest(
+ $request->withQueryParams($parameters),
+ $consumerSecret,
+ $tokenSecret,
+ $parameters['oauth_signature_method']
+ );
+
+ return $signature === $signatureToVerify;
+ }
+
+ /**
+ * Extracts the oauth parameters either from the Authorization header or
+ * from the query string.
+ */
+ public static function extractParameters(Request $request): array
+ {
+ $parameters = $request->getQueryParams();
+
+ $header = $request->getHeaderLine('Authorization');
+ if ($header && str_starts_with($header, 'OAuth ')) {
+ $temp = substr($header, 6);
+ $chunks = explode(',', $temp);
+
+ foreach ($chunks as $chunk) {
+ [$key, $value] = explode('=', $chunk, 2);
+ $value = trim($value, '"');
+ $parameters[$key] = self::urldecode($value);
+ }
+ }
+
+ return $parameters;
+ }
+
+ /**
+ * Creates the base string for the signature. It consists of:
+ *
+ * - The uppercase request method
+ * - The request URL
+ * - the sorted and urlencoded parameters of the request
+ *
+ * The urlencoded parts are concatenated together into a single string
+ * separated by the '&' character.
+ *
+ *
+ */
+ public static function getSignatureBaseString(Request $request): string
+ {
+ $parameters = $request->getQueryParams();
+ ksort($parameters);
+
+ return implode('&', array_map(
+ self::urlencode(...),
+ [
+ strtoupper($request->getMethod()),
+ (string) $request->getUri()->withQuery(''),
+ http_build_query($parameters, '', '&', PHP_QUERY_RFC3986),
+ ]
+ ));
+ }
+
+ /**
+ * Hashes a given text with a given key by the given method.
+ *
+ * @throws RuntimeException if the given hash method is not supported
+ */
+ public static function hash(string $method, string $text, string $key): string
+ {
+ $method = strtolower($method);
+ return match ($method) {
+ 'hmac-sha1', 'sha1' => base64_encode(hash_hmac('sha1', $text, $key, true)),
+ 'hmac-sha256', 'sha256' => base64_encode(hash_hmac('sha256', $text, $key, true)),
+ 'hmac-sha512', 'sha512' => base64_encode(hash_hmac('sha512', $text, $key, true)),
+
+ 'plaintext' => $key,
+
+ default => throw new RuntimeException('Unsupported sigature method "' . $method . '"'),
+ };
+ }
+
+ /**
+ * Urlencodes a given input
+ */
+ public static function urldecode(string $input): string
+ {
+ return rawurldecode($input);
+ }
+
+ /**
+ * Urldecodes a given input
+ */
+ public static function urlencode(string $input): string
+ {
+ $encoded = rawurlencode($input);
+ return str_starts_with($encoded, '/%7E')
+ ? str_replace('/%7E', '/~', $encoded)
+ : $encoded;
+ }
+}
diff --git a/lib/classes/auth_plugins/StudipAuthLTI.class.php b/lib/classes/auth_plugins/StudipAuthLTI.class.php
index e8c316f..07ab8c3 100644
--- a/lib/classes/auth_plugins/StudipAuthLTI.class.php
+++ b/lib/classes/auth_plugins/StudipAuthLTI.class.php
@@ -9,8 +9,12 @@
* the License, or (at your option) any later version.
*/
+use Studip\OAuth2\NegotiatesWithPsr7;
+
class StudipAuthLTI extends StudipAuthSSO
{
+ use NegotiatesWithPsr7;
+
public $consumer_keys;
public $username;
public $domain;
@@ -62,24 +66,15 @@ class StudipAuthLTI extends StudipAuthSSO
*
* @return bool true if authentication succeeds
*
- * @throws OAuthException2 if the signature verification failed
- *
*/
public function isAuthenticated($username, $password)
{
- require_once 'vendor/oauth-php/library/OAuthRequestVerifier.php';
-
- OAuthStore::instance('PDO', [
- 'dsn' => 'mysql:host=' . $GLOBALS['DB_STUDIP_HOST'] . ';dbname=' . $GLOBALS['DB_STUDIP_DATABASE'],
- 'username' => $GLOBALS['DB_STUDIP_USER'],
- 'password' => $GLOBALS['DB_STUDIP_PASSWORD']
- ]);
-
$consumer_key = Request::get('oauth_consumer_key');
$consumer_secret = $this->consumer_keys[$consumer_key]['consumer_secret'];
- $oarv = new OAuthRequestVerifier();
- $oarv->verifySignature($consumer_secret, false, false);
+ if (!Studip\OAuth1::verifyRequest($this->getPsrRequest(), $consumer_secret, '')) {
+ return false;
+ }
return parent::isAuthenticated($username, $password);
}
@@ -93,8 +88,6 @@ class StudipAuthLTI extends StudipAuthSSO
* @param string $password the password (ignored)
*
* @return mixed if authentication succeeds: the Stud.IP user, else false
- *
- * @throws OAuthException2 if the signature verification failed
*/
public function authenticateUser($username, $password)
{