HTTP-запросы к Moodle Outcome Service не выполняются случайно - PullRequest
0 голосов
/ 11 мая 2018

В настоящее время я разрабатываю игру-викторину для внешнего инструмента в Moodle. Я следую спецификации IMS LTI и использую OAuth для аутентификации, как это требуется.

Недавно мне удалось аутентифицировать POST-запрос запуска от Moodle, и теперь я пытаюсь отправить оценки обратно в Moodle из моего инструмента с помощью LTI Basic Outcome Service.

Вот где я столкнулся с проблемой: я создаю сообщения POX, подписываю запрос и отправляю его, но по какой-то причине они выполняются один раз каждые несколько попыток (30% времени, более или менее). Для остальных из них Moodle отвечает сообщением о сбое POX, которое включает в себя «Подпись сообщения недействительна» в качестве описания.

Очевидно, я хочу верить, что мои запросы будут выполняться всегда, когда они мне нужны.

В качестве иллюстрации я покажу вам ниже пример успешного и неудавшегося запроса, а также базовую строку каждого (тот, который OAuth принимает для подписания):

Для успешного запроса:

  1. Базовая строка запроса (разрывы строк только для отображения):

POST&http%3A%2F%2F127.0.0.1%2Fmoodle%2Fmod%2Flti%2Fservice.php &oauth_body_hash%3DhPssgohenJEvtKta2so7Y27p3kU%253D%26oauth_callback%3Dabout%253Ablank %26oauth_consumer_key%3Dkey%26oauth_nonce%3D63cc0764c4cc4701abe28fa5fd406378 %26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1525940779%26oauth_version%3D1.0

  1. Тело ответа:

    <?xml version="1.0" encoding="UTF-8"?>
    <imsx_POXEnvelopeResponse xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
        <imsx_POXHeader>
            <imsx_POXResponseHeaderInfo>
                <imsx_version>V1.0</imsx_version>
                <imsx_messageIdentifier>1602471533</imsx_messageIdentifier>
                <imsx_statusInfo>
                    <imsx_codeMajor>success</imsx_codeMajor>
                    <imsx_severity>status</imsx_severity>
                    <imsx_description>Result read</imsx_description>
                    <imsx_messageRefIdentifier>1339905165</imsx_messageRefIdentifier>
                    <imsx_operationRefIdentifier>readResultRequest</imsx_operationRefIdentifier>
                </imsx_statusInfo>
            </imsx_POXResponseHeaderInfo>
        </imsx_POXHeader>
        <imsx_POXBody>
            <readResultResponse>
                <result>
                    <resultScore>
                        <language>en</language>
                        <textString>0.5</textString>
                    </resultScore>
                </result>
            </readResultResponse>
        </imsx_POXBody>
    </imsx_POXEnvelopeResponse>
    

Для неудавшегося запроса:

  1. Базовая строка запроса (разрывы строк только для отображения):

POST&http%3A%2F%2F127.0.0.1%2Fmoodle%2Fmod%2Flti%2Fservice.php &oauth_body_hash%3DYLigJE%252B8wr7rCwOITqdc1IP3zFs%253D%26oauth_callback%3Dabout%253Ablank %26oauth_consumer_key%3Dkey%26oauth_nonce%3D6de4380ce2ab4d9a90e3fe1723dc5141 %26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1525940816%26oauth_version%3D1.0

  1. Тело ответа:

    <?xml version="1.0" encoding="UTF-8"?>
    <imsx_POXEnvelopeResponse xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
        <imsx_POXHeader>
            <imsx_POXResponseHeaderInfo>
                <imsx_version>V1.0</imsx_version>
                <imsx_messageIdentifier>1688463600</imsx_messageIdentifier>
                <imsx_statusInfo>
                    <imsx_codeMajor>failure</imsx_codeMajor>
                    <imsx_severity>status</verity>
                    <imsx_description>Message signature not valid</imsx_description>
                    <imsx_messageRefIdentifier/>
                    <imsx_operationRefIdentifier>unknownRequest</imsx_operationRefIdentifier>
                </imsx_statusInfo>
            </imsx_POXResponseHeaderInfo>
        </imsx_POXHeader>
        <imsx_POXBody>
            <unknownResponse/>
        </imsx_POXBody>
    </imsx_POXEnvelopeResponse>
    

Как видите, обе базовые строки очень похожи, и я могу найти различия только в тех частях, которые должны быть разными (например, отметка времени или одноразовый номер), поэтому я не могу понять, почему некоторые запросы быть отклоненным, в то время как другие принимаются.

Есть какие-нибудь идеи о причине, по которой это может происходить? Любые предложения о том, как узнать?

Заранее благодарю за помощь.

РЕДАКТИРОВАТЬ: я добавляю сюда код, который я использую в процессе подписи

private IEnumerator SendGradeRequest(XmlDocument xml)
{
    string url = parametrosIniciales["lis_outcome_service_url"];

    byte[] entityBody = Encoding.UTF8.GetBytes(xml.OuterXml); 
    string bodyHash = oAuth.GetBodyHash(entityBody);
    Dictionary<string, string> oAuthParameters = oAuth.PrepareOAuthParameters(oAuth.GetSessionOAuthParameters(parametrosIniciales));
    oAuthParameters.Add("oauth_body_hash", bodyHash);

    Dictionary<string, string> headers = new Dictionary<string, string>();
    headers.Add("Content-Type", "application/xml");
    headers.Add("Authorization", oAuth.GetAuthorizationHeader(oAuthParameters));

    string signature = oAuth.GetRequestSignature(url, headers, entityBody); 
    oAuthParameters.Add("oauth_signature", signature);
    headers["Authorization"] = oAuth.GetAuthorizationHeader(oAuthParameters);

    WWW web = new WWW(url, entityBody, headers);

    // ... Code to manage the response
}

public string GetRequestSignature(string url, Dictionary<string, string> headers, byte[] body)
{
    string baseString = GetBaseString(url, headers, body);
    string key = GetKeyParts(JsonToDict(GameManager.Instance.parametrosIniciales));
    return ComputeHMACSHA1(key, baseString);
}

private string GetBaseString(string url, Dictionary<string, string> headers, object body = null)
{
    string[] parts = {  UrlEncodeRFC3986 (GetNormalizedHttpMethod(body)),
                        UrlEncodeRFC3986 (GetNormalizedHttpURL(url)),
                        UrlEncodeRFC3986 (GetSignableParameters(url, headers, body)) };
    return String.Join("&", parts);
}

private string GetKeyParts(Dictionary<string, string> oAuthParameters)
{
    string consumer, token, consumerSecret, tokenSecret;

    if (oAuthParameters.TryGetValue("oauth_consumer_key", out consumer))
        consumerSecret = UrlEncodeRFC3986(secrets[consumer]);
    else
        throw new Exception("Not consumer key found. If not using any encription, this method should not be called");

    if (oAuthParameters.TryGetValue("oauth_token", out token))
        tokenSecret = UrlEncodeRFC3986(secrets[token]);
    else
        tokenSecret = "";

    return consumerSecret + "&" + tokenSecret;
}

private string GetSignableParameters(string url, Dictionary<string, string> headers, object body = null)
{
    List<String> parametros = new List<string>();

    // 1. Parameters in the OAuth HTTP Authorization header excluding the realm parameter
    string oAuthHeader;
    if (headers.TryGetValue("Authorization", out oAuthHeader))
    {
        oAuthHeader = oAuthHeader.Substring(oAuthHeader.IndexOf(" ") + 1).Replace("\"", "");
        string[] pairs = oAuthHeader.Split(',');

        for (int i = 0; i < pairs.Length; i++)
        {
            string pair = pairs[i];
            string[] temp = pair.Split(new char[] { '=' }, 2);
            temp = UrlEncodeRFC3986(temp);
            pairs[i] = String.Join("=", temp);
        }
        parametros.InsertRange(parametros.Count, pairs);
    }

    // 2. HTTP GET parameters added to the URLs in the query part (as defined by [RFC3986] section 3)
    if (url.Contains("?"))
        parametros.InsertRange(parametros.Count, url.Substring(url.IndexOf("?")).Split('&'));

    // 3. Parameters in the HTTP POST request body (with a content-type of application/x-www-form-urlencoded)
    if (body != null && headers["Content-Type"] == "application/x-www-form-urlencoded" && body is Dictionary<string, string>)
    {
        Dictionary<string, string> cuerpo = RemoveSpareParameters((Dictionary<string, string>)body);
        parametros.InsertRange(parametros.Count, DictToPairsList(cuerpo, true));
    }

    // 4. Sort all parameters by lexicographical value
    parametros.Sort();

    // 5. Concatenate all parameters with '&'
    return String.Join("&", parametros.ToArray());
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...