Commit 7eadb691 authored by Piers Harding's avatar Piers Harding Committed by Aaron Wells
Browse files

Bug 1393536: add OAuth LTI support

behatnotneeded

Change-Id: I224fcad3a4be6b54e4b48c07392a9415e22738b6
parent 9d565e59
......@@ -53,6 +53,89 @@ class mahara_user_external extends external_api {
'industry',
);
/**
* parameter definition for input of delete_users method
*
* Returns description of method parameters
* @return external_function_parameters
*/
public static function autologin_redirect_parameters() {
return new external_function_parameters(
array(
'context_id' => new external_value(PARAM_RAW, 'LTI context_id', VALUE_OPTIONAL),
'context_label' => new external_value(PARAM_RAW, 'LTI context_label', VALUE_OPTIONAL),
'context_title' => new external_value(PARAM_RAW, 'LTI context_title', VALUE_OPTIONAL),
'context_type' => new external_value(PARAM_RAW, 'LTI context_type', VALUE_OPTIONAL),
'ext_lms' => new external_value(PARAM_RAW, 'LTI ext_lms', VALUE_OPTIONAL),
'ext_user_username' => new external_value(PARAM_RAW, 'LTI ext_user_username', VALUE_OPTIONAL),
'launch_presentation_locale' => new external_value(PARAM_RAW, 'LTI launch_presentation_locale', VALUE_OPTIONAL),
'launch_presentation_return_url' => new external_value(PARAM_RAW, 'LTI launch_presentation_return_url', VALUE_OPTIONAL),
'lis_person_contact_email_primary' => new external_value(PARAM_RAW, 'LTI lis_person_contact_email_primary', VALUE_OPTIONAL),
'lis_person_name_family' => new external_value(PARAM_RAW, 'LTI lis_person_name_family', VALUE_OPTIONAL),
'lis_person_name_full' => new external_value(PARAM_RAW, 'LTI lis_person_name_full', VALUE_OPTIONAL),
'lis_person_name_given' => new external_value(PARAM_RAW, 'LTI lis_person_name_given', VALUE_OPTIONAL),
'lis_person_sourcedid' => new external_value(PARAM_RAW, 'LTI lis_person_sourcedid', VALUE_OPTIONAL),
'lti_message_type' => new external_value(PARAM_RAW, 'LTI lti_message_type', VALUE_OPTIONAL),
'lti_version' => new external_value(PARAM_RAW, 'LTI lti_version', VALUE_OPTIONAL),
'resource_link_description' => new external_value(PARAM_RAW, 'LTI resource_link_description', VALUE_OPTIONAL),
'resource_link_id' => new external_value(PARAM_RAW, 'LTI resource_link_id', VALUE_OPTIONAL),
'resource_link_title' => new external_value(PARAM_RAW, 'LTI resource_link_title', VALUE_OPTIONAL),
'roles' => new external_value(PARAM_RAW, 'LTI roles', VALUE_OPTIONAL),
'tool_consumer_info_product_family_code' => new external_value(PARAM_RAW, 'LTI tool_consumer_info_product_family_code', VALUE_OPTIONAL),
'tool_consumer_info_version' => new external_value(PARAM_RAW, 'LTI tool_consumer_info_version', VALUE_OPTIONAL),
'tool_consumer_instance_guid' => new external_value(PARAM_RAW, 'LTI tool_consumer_instance_guid', VALUE_OPTIONAL),
'tool_consumer_instance_name' => new external_value(PARAM_RAW, 'LTI tool_consumer_instance_name', VALUE_OPTIONAL),
'user_id' => new external_value(PARAM_RAW, 'LTI user_id', VALUE_OPTIONAL),
)
);
}
/**
* Delete one or more users
*
* @param array $params
*/
public static function autologin_redirect($params) {
global $USER, $WEBSERVICE_INSTITUTION, $WEBSERVICE_OAUTH_USER;
require_once(get_config('docroot') . 'artefact/lib.php');
$keys = array_keys(self::autologin_redirect_parameters()->keys);
$params = array_combine($keys, func_get_args());
error_log('in autologin_redirect: '.var_export($params, true));
$user = get_record('usr', 'username', $params['ext_user_username'], 'deleted', 0);
if (empty($user) || empty($user->id) || $user->id < 1) {
// logout
error_log('cant find user - logout');
$USER->logout();
redirect(get_config('wwwroot'));
die();
}
error_log('reanimating: '.var_export($user->username, true));
$USER->reanimate($user->id, $user->authinstance);
if (empty($params['resource_link_id'])) {
error_log('no resource_link_id - now jumping to: '.get_config('wwwroot'));
redirect(get_config('wwwroot'));
}
else {
error_log('now jumping to: '.$params['resource_link_id']);
redirect($params['resource_link_id']);
}
// should not get here
die();
}
/**
* parameter definition for output of autologin_redirect method
*/
public static function autologin_redirect_returns() {
return null;
}
/**
* parameter definition for input of create_users method
*
......
This diff is collapsed.
......@@ -441,7 +441,7 @@ class external_api {
$result[$key] = self::validate_parameters($subdesc, $params[$key]);
} catch (WebserviceInvalidParameterException $e) {
//it's ok to display debug info as here the information is useful for ws client/dev
throw new WebserviceParameterException('invalidextparam',"key: $key (debuginfo: " . $e->debuginfo.") ");
throw new WebserviceParameterException('invalidextparam',"key: $key - ".$e->getMessage().(isset($e->debuginfo) ? " (debuginfo: " . $e->debuginfo.") " : ""));
}
}
unset($params[$key]);
......@@ -738,7 +738,6 @@ abstract class webservice_server implements webservice_server_interface {
*/
protected function authenticate_user() {
global $USER, $SESSION, $WEBSERVICE_INSTITUTION, $WEBSERVICE_OAUTH_USER;
if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
$this->auth = 'USER';
//we check that authentication plugin is enabled
......@@ -802,10 +801,8 @@ abstract class webservice_server implements webservice_server_interface {
safe_require('auth', 'webservice');
// get the user - the user that authorised the token
$user = get_record('usr', 'id', $this->oauth_token_details['user_id']);
if (empty($user)) {
throw new WebserviceAccessException(get_string('wrongusernamepassword', 'auth.webservice'));
}
$user = $this->authenticate_by_token(EXTERNAL_TOKEN_OAUTH1);
// check user is member of configured OAuth institution
$institutions = array_keys(load_user_institutions($this->oauth_token_details['user_id']));
$auth_instance = get_record('auth_instance', 'id', $user->authinstance);
......@@ -837,7 +834,14 @@ abstract class webservice_server implements webservice_server_interface {
protected function authenticate_by_token($tokentype) {
global $WEBSERVICE_INSTITUTION;
if ($tokentype == EXTERNAL_TOKEN_PERMANENT || $tokentype == EXTERNAL_TOKEN_USER) {
if ($tokentype == EXTERNAL_TOKEN_OAUTH1) {
$user = get_record('usr', 'id', $this->oauth_token_details['user_id']);
if (empty($user)) {
throw new WebserviceAccessException(get_string('wrongusernamepassword', 'auth.webservice'));
}
return $user;
}
else if ($tokentype == EXTERNAL_TOKEN_PERMANENT || $tokentype == EXTERNAL_TOKEN_USER) {
$token = get_record('external_tokens', 'token', $this->token);
// trap personal tokens with no valid until time set
if ($token && $token->tokentype == EXTERNAL_TOKEN_USER && $token->validuntil == 0 && ((strtotime($token->ctime) - time()) > EXTERNAL_TOKEN_USER_EXPIRES)) {
......@@ -1761,10 +1765,17 @@ abstract class webservice_base_server extends webservice_server {
*/
protected function execute() {
// validate params, this also sorts the params properly, we need the correct order in the next part
ksort($this->parameters);
error_log('going to run validate_parameters against: '.var_export($this->parameters, true));
$params = call_user_func(array($this->function->classname, 'validate_parameters'), $this->function->parameters_desc, $this->parameters);
error_log('after parms');
// execute - yay!
error_log('executing: '.$this->function->classname."/".$this->function->methodname);
$this->returns = call_user_func_array(array($this->function->classname, $this->function->methodname), array_values($params));
error_log('after execute: '.var_export($this->returns, true));
}
}
......@@ -2049,7 +2060,7 @@ class WebserviceInvalidResponseException extends MaharaException {
* @param string $debuginfo some detailed information
*/
function __construct($debuginfo=null) {
parent::__construct(get_string('invalidresponse', 'auth.webservice', $a) . $debuginfo);
parent::__construct(get_string('invalidresponse', 'auth.webservice', $debuginfo) . $debuginfo);
}
}
......
......@@ -655,8 +655,12 @@ class OAuthRequest
}
else
{
return $this->urlencode(rawurldecode($s));
// return $this->urlencode(urldecode($s));
if (!isset($_SERVER['CONTENT_TYPE']) || $_SERVER['CONTENT_TYPE'] == 'application/x-www-form-urlencoded') {
return $this->urlencode(urldecode($s));
}
else {
return $this->urlencode(rawurldecode($s));
}
}
}
......
......@@ -3,26 +3,26 @@
/**
* Verify the current request. Checks if signed and if the signature is correct.
* When correct then also figures out on behalf of which user this request is being made.
*
*
* @version $Id: OAuthRequestVerifier.php 155 2010-09-10 18:38:33Z brunobg@corollarium.com $
* @author Marc Worrell <marcw@pobox.com>
* @date Nov 16, 2007 4:35:03 PM
*
*
*
*
* The MIT License
*
*
* Copyright (c) 2007-2008 Mediamatic Lab
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......@@ -41,10 +41,10 @@ class OAuthRequestVerifier extends OAuthRequest
private $request;
private $store;
private $accepted_signatures = null;
/**
* Construct the request to be verified
*
*
* @param string request
* @param string method
* @param array params The request parameters
......@@ -54,24 +54,24 @@ class OAuthRequestVerifier extends OAuthRequest
if ($params) {
$encodedParams = array();
foreach ($params as $key => $value) {
if (preg_match("/^oauth_/", $key)) {
if (preg_match("/^oauth_/", $key)) {
continue;
}
$encodedParams[rawurlencode($key)] = rawurlencode($value);
}
$this->param = array_merge($this->param, $encodedParams);
}
$this->store = OAuthStore::instance();
parent::__construct($uri, $method);
OAuthRequestLogger::start($this);
}
/**
* See if the current request is signed with OAuth
*
*
* @return boolean
*/
static public function requestIsSigned ()
......@@ -98,7 +98,7 @@ class OAuthRequestVerifier extends OAuthRequest
/**
* Verify the request if it seemed to be signed.
*
*
* @param string token_type the kind of token needed, defaults to 'access'
* @exception OAuthException2 thrown when the request did not verify
* @return boolean true when signed, false when not signed
......@@ -123,26 +123,26 @@ class OAuthRequestVerifier extends OAuthRequest
/**
* Verify the request
*
*
* @param string token_type the kind of token needed, defaults to 'access' (false, 'access', 'request')
* @exception OAuthException2 thrown when the request did not verify
* @return int user_id associated with token (false when no user associated)
*/
public function verify ( $token_type = 'access' )
public function verify ( $token_type = 'access' )
{
$retval = $this->verifyExtended($token_type);
return $retval['user_id'];
}
/**
* Verify the request
*
*
* @param string token_type the kind of token needed, defaults to 'access' (false, 'access', 'request')
* @exception OAuthException2 thrown when the request did not verify
* @return array ('user_id' => associated with token (false when no user associated),
* 'consumer_key' => the associated consumer_key)
*
*
*/
public function verifyExtended ( $token_type = 'access' )
{
......@@ -153,8 +153,8 @@ class OAuthRequestVerifier extends OAuthRequest
if ($consumer_key && ($token_type === false || $token))
{
$secrets = $this->store->getSecretsForVerify( $this->urldecode($consumer_key),
$this->urldecode($token),
$secrets = $this->store->getSecretsForVerify( $this->urldecode($consumer_key),
$this->urldecode($token),
$token_type);
$this->store->checkServerNonce( $this->urldecode($consumer_key),
......@@ -166,18 +166,18 @@ class OAuthRequestVerifier extends OAuthRequest
if (empty($oauth_sig))
{
throw new OAuthException2('Verification of signature failed (no oauth_signature in request).');
}
}
try
{
$this->verifySignature($secrets['consumer_secret'], $secrets['token_secret'], $token_type);
}
catch (OAuthException2 $e)
{
throw new OAuthException2('Verification of signature failed (signature base string was "'.$this->signatureBaseString().'").'
throw new OAuthException2('Verification of signature failed (signature base string was "'.$this->signatureBaseString().'").'
. " with " . print_r(array($secrets['consumer_secret'], $secrets['token_secret'], $token_type), true));
}
// Check the optional body signature
if ($this->getParam('xoauth_body_signature'))
{
......@@ -196,13 +196,13 @@ class OAuthRequestVerifier extends OAuthRequest
throw new OAuthException2('Verification of body signature failed.');
}
}
// All ok - fetch the user associated with this request
if (isset($secrets['user_id']))
{
$user_id = $secrets['user_id'];
}
// Check if the consumer wants us to reset the ttl of this token
$ttl = $this->getParam('xoauth_token_ttl', true);
if (is_numeric($ttl))
......@@ -214,19 +214,19 @@ class OAuthRequestVerifier extends OAuthRequest
{
throw new OAuthException2('Can\'t verify request, missing oauth_consumer_key or oauth_token');
}
return array('user_id' => $user_id, 'consumer_key' => $consumer_key, 'osr_id' => $secrets['osr_id']);
return array('user_id' => $user_id, 'consumer_key' => $consumer_key, 'osr_id' => $secrets['osr_id']);
}
/**
* Verify the signature of the request, using the method in oauth_signature_method.
* The signature is returned encoded in the form as used in the url. So the base64 and
* urlencoding has been done.
*
*
* @param string consumer_secret
* @param string token_secret
* @exception OAuthException2 thrown when the signature method is unknown
* @exception OAuthException2 thrown when the signature method is unknown
* @exception OAuthException2 when not all parts available
* @exception OAuthException2 when signature does not match
*/
......@@ -263,13 +263,13 @@ class OAuthRequestVerifier extends OAuthRequest
/**
* Verify the signature of a string.
*
*
* @param string data
* @param string consumer_secret
* @param string token_secret
* @param string signature_method
* @param string signature
* @exception OAuthException2 thrown when the signature method is unknown
* @exception OAuthException2 thrown when the signature method is unknown
* @exception OAuthException2 when signature does not match
*/
public function verifyDataSignature ( $data, $consumer_secret, $token_secret, $signature_method, $signature )
......@@ -287,10 +287,10 @@ class OAuthRequestVerifier extends OAuthRequest
}
/**
*
* @param array $accepted The array of accepted signature methods, or if null is passed
*
* @param array $accepted The array of accepted signature methods, or if null is passed
* all supported methods are accepted and there is no filtering.
*
*
*/
public function setAcceptedSignatureMethods($accepted = null) {
if (is_array($accepted))
......
......@@ -2,25 +2,25 @@
/**
* OAuth signature implementation using HMAC-SHA1
*
*
* @version $Id$
* @author Marc Worrell <marcw@pobox.com>
* @date Sep 8, 2008 12:21:19 PM
*
*
* The MIT License
*
*
* Copyright (c) 2007-2008 Mediamatic Lab
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......@@ -45,12 +45,12 @@ class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod
/**
* Calculate the signature using HMAC-SHA1
* This function is copyright Andy Smith, 2007.
*
*
* @param OAuthRequest request
* @param string base_string
* @param string consumer_secret
* @param string token_secret
* @return string
* @return string
*/
function signature ( $request, $base_string, $consumer_secret, $token_secret )
{
......@@ -87,7 +87,7 @@ class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod
/**
* Check if the request signature corresponds to the one calculated for the request.
*
*
* @param OAuthRequest request
* @param string base_string data to be signed, usually the base string, can be a request body
* @param string consumer_secret
......
......@@ -661,14 +661,19 @@ class OAuthStoreMahara extends OAuthStoreAbstract {
// Insert the new combination
$timestamp_fmt = db_format_timestamp($timestamp);
$result = execute_sql('
INSERT INTO {oauth_server_nonce}
( consumer_key,
token,
ctime,
nonce )
VALUES (?, ?, ?, ?)
', array($consumer_key, $token, $timestamp_fmt, $nonce));
try {
$result = execute_sql('
INSERT INTO {oauth_server_nonce}
( consumer_key,
token,
ctime,
nonce )
VALUES (?, ?, ?, ?)
', array($consumer_key, $token, $timestamp_fmt, $nonce));
}
catch (Exception $e) {
$result = false;
}
if (!$result) {
throw new OAuthException2('Duplicate timestamp/nonce combination, possible replay attack. Request rejected.');
......
<?php
/**
* OAuthSession is a really *dirty* storage. It's useful for testing and may
* OAuthSession is a really *dirty* storage. It's useful for testing and may
* be enough for some very simple applications, but it's not recommended for
* production use.
*
*
* @version $Id: OAuthStoreSession.php 153 2010-08-30 21:25:58Z brunobg@corollarium.com $
* @author BBG
*
*
* The MIT License
*
*
* Copyright (c) 2007-2008 Mediamatic Lab
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......@@ -35,7 +35,7 @@ require_once dirname(__FILE__) . '/OAuthStoreAbstract.class.php';
class OAuthStoreSession extends OAuthStoreAbstract
{
private $session;
private $session;
/*
* Takes two options: consumer_key and consumer_secret
......@@ -55,6 +55,7 @@ class OAuthStoreSession extends OAuthStoreAbstract
$session_array['authorize_uri'] = $options['authorize_uri'];
$session_array['access_token_uri'] = $options['access_token_uri'];
$this->session = $session_array;
error_log('setting $SESSION values - oauth');
$SESSION->set('oauth_' . $options['consumer_key'], $this->session);
}
......@@ -65,16 +66,16 @@ class OAuthStoreSession extends OAuthStoreAbstract
}
public function getSecretsForVerify ( $consumer_key, $token, $token_type = 'access' ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }
public function getSecretsForSignature ( $uri, $user_id )
public function getSecretsForSignature ( $uri, $user_id )
{
return $this->session;
}
public function getServerTokenSecrets ( $consumer_key, $token, $token_type, $user_id, $name = '')
{
public function getServerTokenSecrets ( $consumer_key, $token, $token_type, $user_id, $name = '')
{
if ($consumer_key != $this->session['consumer_key']) {
return array();
}
}
return array(
'consumer_key' => $consumer_key,
'consumer_secret' => $this->session['consumer_secret'],
......@@ -89,8 +90,8 @@ class OAuthStoreSession extends OAuthStoreAbstract
'token_ttl' => 3600,
);
}
public function addServerToken ( $consumer_key, $token_type, $token, $token_secret, $user_id, $options = array() )
public function addServerToken ( $consumer_key, $token_type, $token, $token_secret, $user_id, $options = array() )
{
global $SESSION;
......@@ -101,8 +102,8 @@ class OAuthStoreSession extends OAuthStoreAbstract
}
public function deleteServer ( $consumer_key, $user_id, $user_is_admin = false ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }
public function getServer( $consumer_key, $user_id, $user_is_admin = false ) {
return array(
public function getServer( $consumer_key, $user_id, $user_is_admin = false ) {
return array(
'id' => 0,
'user_id' => $user_id,
'consumer_key' => $this->session['consumer_key'],
......@@ -114,20 +115,20 @@ class OAuthStoreSession extends OAuthStoreAbstract
'access_token_uri' => $this->session['access_token_uri'],
);
}
public function getServerForUri ( $uri, $user_id ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }
public function listServerTokens ( $user_id ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }
public function countServerTokens ( $consumer_key ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }
public function getServerToken ( $consumer_key, $token, $user_id ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }
public function deleteServerToken ( $consumer_key, $token, $user_id, $user_is_admin = false ) {
// TODO
// TODO
}
public function setServerTokenTtl ( $consumer_key, $token, $token_ttl )
{
//This method just needs to exist. It doesn't have to do anything!
}
public function listServers ( $q = '', $user_id ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }
public function updateServer ( $server, $user_id, $user_is_admin = false ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }
......@@ -145,17 +146,17 @@ class OAuthStoreSession extends OAuthStoreAbstract
public function getConsumerAccessToken ( $token, $user_id ) { throw new OAuthException2("OAuthStoreSession doesn't support " . __METHOD__); }