示例#1
0
def build_signature(request, consumer_secret):
    """Uses the request object and consumer secret to build a signature

    This is an internal function and it's highly unlikely than an end
    user would ever need to call this

    :param request         : a twolegged.Request subclass
    :param consumer_secret : string
    :rtype                 : string

    """
    headers = request.headers()
    auth_headers = {k: v for k, v in headers.iteritems() if k == 'Authorization'}
    # we are only interested in the POST data if it's passed as form
    # parameters while raw POST body is ignored
    body = request.form_data()
    params = [(k, v) for k, v in request.params() if k != 'oauth_signature']
    qs = RequestEncodingMixin._encode_params(params)
    collected_params = collect_parameters(qs, body, auth_headers)
    normalized_params = normalize_parameters(collected_params)
    host = headers.get('Host', None)
    normalized_uri = normalize_base_string_uri(request.base_url(), host)
    base_string = construct_base_string(unicode(request.method()),
                                        normalized_uri,
                                        normalized_params)
    return sign_hmac_sha1(base_string, consumer_secret, None)
示例#2
0
文件: lti.py 项目: eliesmr4/myedx
    def _get_validated_lti_params_from_values(cls, request, current_time,
                                              lti_consumer_valid,
                                              lti_consumer_secret,
                                              lti_max_timestamp_age):
        """
        Validates LTI signature and returns LTI parameters
        """

        # Taking a cue from oauthlib, to avoid leaking information through a timing attack,
        # we proceed through the entire validation before rejecting any request for any reason.
        # However, as noted there, the value of doing this is dubious.
        try:
            base_uri = normalize_base_string_uri(request.uri)
            parameters = collect_parameters(uri_query=request.uri_query,
                                            body=request.body)
            parameters_string = normalize_parameters(parameters)
            base_string = construct_base_string(request.http_method, base_uri,
                                                parameters_string)

            computed_signature = sign_hmac_sha1(base_string,
                                                unicode(lti_consumer_secret),
                                                '')
            submitted_signature = request.oauth_signature

            data = {
                parameter_value_pair[0]: parameter_value_pair[1]
                for parameter_value_pair in parameters
            }

            def safe_int(value):
                """
                Interprets parameter as an int or returns 0 if not possible
                """
                try:
                    return int(value)
                except (ValueError, TypeError):
                    return 0

            oauth_timestamp = safe_int(request.oauth_timestamp)

            # As this must take constant time, do not use shortcutting operators such as 'and'.
            # Instead, use constant time operators such as '&', which is the bitwise and.
            valid = (lti_consumer_valid)
            valid = valid & (submitted_signature == computed_signature)
            valid = valid & (request.oauth_version == '1.0')
            valid = valid & (request.oauth_signature_method == 'HMAC-SHA1')
            valid = valid & (
                'user_id' in data
            )  # Not required by LTI but can't log in without one
            valid = valid & (oauth_timestamp >=
                             current_time - lti_max_timestamp_age)
            valid = valid & (oauth_timestamp <= current_time)
            if valid:
                return data
        except AttributeError as error:
            log.error("'{}' not found.".format(error.message))
        return None
    def test_normalize_base_string_uri(self):
        """
        Example text to be turned into a normalized base string uri::

            GET /?q=1 HTTP/1.1
            Host: www.example.net:8080

        Sample string generated::

            https://www.example.net:8080/
        """

        # test for unicode failure
        uri = b"www.example.com:8080"
        self.assertRaises(ValueError, normalize_base_string_uri, uri)

        # test for missing scheme
        uri = "www.example.com:8080"
        self.assertRaises(ValueError, normalize_base_string_uri, uri)

        # test a URI with the default port
        uri = "http://www.example.com:80/"
        self.assertEqual(normalize_base_string_uri(uri),
                         "http://www.example.com/")

        # test a URI missing a path
        uri = "http://www.example.com"
        self.assertEqual(normalize_base_string_uri(uri),
                         "http://www.example.com/")

        # test a relative URI
        uri = "/a-host-relative-uri"
        host = "www.example.com"
        self.assertRaises(ValueError, normalize_base_string_uri, (uri, host))

        # test overriding the URI's netloc with a host argument
        uri = "http://www.example.com/a-path"
        host = "alternatehost.example.com"
        self.assertEqual(normalize_base_string_uri(uri, host),
                         "http://alternatehost.example.com/a-path")
示例#4
0
    def test_normalize_base_string_uri(self):
        """
        Example text to be turned into a normalized base string uri::

            GET /?q=1 HTTP/1.1
            Host: www.example.net:8080

        Sample string generated::

            https://www.example.net:8080/
        """

        # test for unicode failure
        uri = b"www.example.com:8080"
        self.assertRaises(ValueError, normalize_base_string_uri, uri)

        # test for missing scheme
        uri = "www.example.com:8080"
        self.assertRaises(ValueError, normalize_base_string_uri, uri)

        # test a URI with the default port
        uri = "http://www.example.com:80/"
        self.assertEqual(normalize_base_string_uri(uri),
                         "http://www.example.com/")

        # test a URI missing a path
        uri = "http://www.example.com"
        self.assertEqual(normalize_base_string_uri(uri),
                         "http://www.example.com/")

        # test a relative URI
        uri = "/a-host-relative-uri"
        host = "www.example.com"
        self.assertRaises(ValueError, normalize_base_string_uri, (uri, host))

        # test overriding the URI's netloc with a host argument
        uri = "http://www.example.com/a-path"
        host = "alternatehost.example.com"
        self.assertEqual(normalize_base_string_uri(uri, host),
                         "http://alternatehost.example.com/a-path")
示例#5
0
    def _get_validated_lti_params_from_values(cls, request, current_time,
                                              lti_consumer_valid, lti_consumer_secret, lti_max_timestamp_age):
        """
        Validates LTI signature and returns LTI parameters
        """

        # Taking a cue from oauthlib, to avoid leaking information through a timing attack,
        # we proceed through the entire validation before rejecting any request for any reason.
        # However, as noted there, the value of doing this is dubious.
        try:
            base_uri = normalize_base_string_uri(request.uri)
            parameters = collect_parameters(uri_query=request.uri_query, body=request.body)
            parameters_string = normalize_parameters(parameters)
            base_string = construct_base_string(request.http_method, base_uri, parameters_string)

            computed_signature = sign_hmac_sha1(base_string, unicode(lti_consumer_secret), '')
            submitted_signature = request.oauth_signature

            data = {parameter_value_pair[0]: parameter_value_pair[1] for parameter_value_pair in parameters}

            def safe_int(value):
                """
                Interprets parameter as an int or returns 0 if not possible
                """
                try:
                    return int(value)
                except (ValueError, TypeError):
                    return 0

            oauth_timestamp = safe_int(request.oauth_timestamp)

            # As this must take constant time, do not use shortcutting operators such as 'and'.
            # Instead, use constant time operators such as '&', which is the bitwise and.
            valid = (lti_consumer_valid)
            valid = valid & (submitted_signature == computed_signature)
            valid = valid & (request.oauth_version == '1.0')
            valid = valid & (request.oauth_signature_method == 'HMAC-SHA1')
            valid = valid & ('user_id' in data)  # Not required by LTI but can't log in without one
            valid = valid & (oauth_timestamp >= current_time - lti_max_timestamp_age)
            valid = valid & (oauth_timestamp <= current_time)
            if valid:
                return data
        except AttributeError as error:
            log.error("'{}' not found.".format(error.message))
        return None
示例#6
0
def message_cb(bot, event):
    source = event.data["source"]["aimId"]
    message = event.data["message"]

    method = url = query = secret_key = None
    for token in re.split(pattern=r"\s+", string=message):
        if method is None and token.upper() in HTTP_METHODS:
            method = token
            continue

        if url is None:
            try:
                parsed = urlparse.urlparse(url=token)
                assert parsed.scheme and parsed.netloc
                url = token
                query = parsed.query
                continue
            except (ValueError, AssertionError):
                pass

        if secret_key is None:
            secret_key = token

    if url is None or query is None:
        help_cb(bot=bot, event=event)
        return

    if method is None:
        method = "METHOD"

    params = urlparse.parse_qsl(query, keep_blank_values=True)
    normalized_params = signature.normalize_parameters(params)
    normalized_url = signature.normalize_base_string_uri(url)
    sign_base = signature.construct_base_string(method, normalized_url, normalized_params)

    sign = None
    if secret_key is not None:
        sign = b64encode(HMAC(secret_key.encode(), sign_base.encode(), hashlib.sha256).digest()).decode()

    bot.send_im(
        target=source,
        message="Signature base: {signature_base}{sign}".format(
            signature_base=sign_base, sign="" if sign is None else "\n\nSignature: {sign}".format(sign=sign)
        )
    )
示例#7
0
def make_args(consumer_key, consumer_secret, launch_url, oauth_timestamp,
              oauth_nonce, extra_args):
    args = {
        'oauth_consumer_key': consumer_key,
        'oauth_timestamp': str(oauth_timestamp),
        'oauth_nonce': oauth_nonce
    }

    args.update(extra_args)

    base_string = signature.construct_base_string(
        'POST', signature.normalize_base_string_uri(launch_url),
        signature.normalize_parameters(
            signature.collect_parameters(body=args, headers={})))

    args['oauth_signature'] = signature.sign_hmac_sha1(base_string,
                                                       consumer_secret, None)

    return args
示例#8
0
 def sign(self, url, method=u'POST', signature_method=u'HMAC-SHA1'):
     """
     Use this method to create a signature over the authorization header, and the url query parameters
     :param url: request url
     :param method: request method (i.e. POST, GET)
     :param signature_method: method of signature. Only supports (HMAC-SHA1, PLAINTEXT)
     Note: that HMAC-SHA1 is required by Yahoo API queries since they are sent insecurely
     """
     # could change this to support additional methods
     # for now only support HMAC-SHA1 and PLAINTEXT (Yahoo only supports these)
     self.add_param(u'oauth_signature_method', signature_method)
     self.add_param(u'oauth_nonce', generate_nonce())
     self.add_param(u'oauth_timestamp', generate_timestamp())
     if signature_method == u'HMAC-SHA1':
         base_string = construct_base_string(unicode(method),
                                             normalize_base_string_uri(unicode(url)),
                                             normalize_parameters(self.params))
         signature = sign_hmac_sha1(base_string, self.client_secret, self.oauth_token_secret)
     else:
         signature = quote(self.client_secret + u'&' + self.oauth_token_secret)
     self.add_param(u'oauth_signature', signature)
示例#9
0
文件: lti.py 项目: yuvipanda/hubtraf
async def lti_login_data(session, log, hub_url, username, consumer_key, consumer_secret, launch_url, extra_args={}):
    """
    Log in username with LTI info to hub_url

    log is used to emit timing and status information.
    """
    args = {
        'oauth_consumer_key': consumer_key,
        'oauth_timestamp': str(time.time()),
        'oauth_nonce': str(uuid.uuid4()),
        'user_id': username
    }

    args.update(extra_args)

    base_string = signature.construct_base_string(
        'POST',
        signature.normalize_base_string_uri(launch_url),
        signature.normalize_parameters(
            signature.collect_parameters(body=args, headers={})
        )
    )

    args['oauth_signature'] = signature.sign_hmac_sha1(base_string, consumer_secret, None)

    url = hub_url / 'lti/launch'

    try:
        resp = await session.post(url, data=args, allow_redirects=False)
    except Exception as e:
        log.msg('Login: Failed with exception {}'.format(repr(e)), action='login', phase='failed', duration=time.monotonic() - start_time)
        raise OperationError()
    if resp.status != 302:
        log.msg('Login: Failed with response {}'.format(str(resp)), action='login', phase='failed', duration=time.monotonic() - start_time)
        raise OperationError()
    return args
示例#10
0
    def validate_launch_request(self, launch_url, headers, args):
        """
        Validate a given launch request

        launch_url: Full URL that the launch request was POSTed to
        headers: k/v pair of HTTP headers coming in with the POST
        args: dictionary of body arguments passed to the launch_url
            Must have the following keys to be valid:
                oauth_consumer_key, oauth_timestamp, oauth_nonce,
                oauth_signature
        """

        # Validate args!
        if 'oauth_consumer_key' not in args:
            raise web.HTTPError(401, "oauth_consumer_key missing")
        if args['oauth_consumer_key'] not in self.consumers:
            raise web.HTTPError(401, "oauth_consumer_key not known")

        if 'oauth_signature' not in args:
            raise web.HTTPError(401, "oauth_signature missing")
        if 'oauth_timestamp' not in args:
            raise web.HTTPError(401, 'oauth_timestamp missing')

        # Allow 30s clock skew between LTI Consumer and Provider
        # Also don't accept timestamps from before our process started, since that could be
        # a replay attack - we won't have nonce lists from back then. This would allow users
        # who can control / know when our process restarts to trivially do replay attacks.
        oauth_timestamp = int(float(args['oauth_timestamp']))
        if (int(time.time()) - oauth_timestamp > 30
                or oauth_timestamp < LTILaunchValidator.PROCESS_START_TIME):
            raise web.HTTPError(401, "oauth_timestamp too old")

        if 'oauth_nonce' not in args:
            raise web.HTTPError(401, 'oauth_nonce missing')
        if (oauth_timestamp in LTILaunchValidator.nonces
                and args['oauth_nonce']
                in LTILaunchValidator.nonces[oauth_timestamp]):
            raise web.HTTPError(401,
                                "oauth_nonce + oauth_timestamp already used")
        LTILaunchValidator.nonces.setdefault(oauth_timestamp,
                                             set()).add(args['oauth_nonce'])

        args_list = []
        for key, values in args.items():
            if type(values) is list:
                args_list += [(key, value) for value in values]
            else:
                args_list.append((key, values))

        base_string = signature.construct_base_string(
            'POST', signature.normalize_base_string_uri(launch_url),
            signature.normalize_parameters(
                signature.collect_parameters(body=args_list, headers=headers)))

        consumer_secret = self.consumers[args['oauth_consumer_key']]

        sign = signature.sign_hmac_sha1(base_string, consumer_secret, None)
        is_valid = signature.safe_string_equals(sign, args['oauth_signature'])

        if not is_valid:
            raise web.HTTPError(401, "Invalid oauth_signature")

        return True
示例#11
0
def post_grade(sourcedid, outcomes_url, consumer_key, consumer_secret, grade):
    # Who is treating XML as Text? I am!
    # WHY WOULD YOU MIX MULTIPART, XML (NOT EVEN JUST XML, BUT WSDL GENERATED POX WTF), AND PARTS OF OAUTH1 SIGNING
    # IN THE SAME STANDARD AAAA!
    # TODO: extract this into a real library with real XML parsing
    # WARNING: You can use this only with data you trust! Beware, etc.
    post_xml = r"""
    <?xml version = "1.0" encoding = "UTF-8"?> 
    <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0"> 
      <imsx_POXHeader> 
        <imsx_POXRequestHeaderInfo> 
          <imsx_version>V1.0</imsx_version> 
          <imsx_messageIdentifier>999999123</imsx_messageIdentifier> 
        </imsx_POXRequestHeaderInfo> 
      </imsx_POXHeader> 
      <imsx_POXBody> 
        <replaceResultRequest> 
          <resultRecord> 
            <sourcedGUID> 
              <sourcedId>{sourcedid}</sourcedId> 
            </sourcedGUID> 
            <result> 
              <resultScore> 
                <language>en</language> 
                <textString>{grade}</textString> 
              </resultScore> 
            </result> 
          </resultRecord> 
        </replaceResultRequest> 
      </imsx_POXBody> 
    </imsx_POXEnvelopeRequest>
    """
    post_data = post_xml.format(grade=float(grade), sourcedid=sourcedid)

    # Yes, we do have to use sha1 :(
    body_hash_sha = sha1()
    body_hash_sha.update(post_data.encode('utf-8'))
    body_hash = base64.b64encode(body_hash_sha.digest()).decode('utf-8')
    args = {
        'oauth_body_hash': body_hash,
        'oauth_consumer_key': consumer_key,
        'oauth_timestamp': str(time.time()),
        'oauth_nonce': str(time.time())
    }

    base_string = signature.construct_base_string(
        'POST',
        signature.normalize_base_string_uri(outcomes_url),
        signature.normalize_parameters(
            signature.collect_parameters(body=args, headers={})
        )
    )

    oauth_signature = signature.sign_hmac_sha1(base_string, consumer_secret, None)
    args['oauth_signature'] = oauth_signature

    headers = parameters.prepare_headers(args, headers={
        'Content-Type': 'application/xml'
    })    

    resp = requests.post(outcomes_url, data=post_data, headers=headers)
    if resp.status_code != 200:
        raise GradePostException(resp)

    response_tree = etree.fromstring(resp.text.encode('utf-8'))

    # XML and its namespaces. UBOOF!
    status_tree = response_tree.find('.//{http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0}imsx_statusInfo')
    code_major = status_tree.find('{http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0}imsx_codeMajor').text

    if code_major != 'success':
        raise GradePostException(resp)
    def validate_request(self,
                         uri,
                         http_method="GET",
                         body=None,
                         headers=None):
        """Validate a signed OAuth request.

        :param uri: The full URI of the token request.
        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
        :param body: The request body as a string.
        :param headers: The request headers as a dict.
        :returns: A tuple of 2 elements.
                  1. True if valid, False otherwise.
                  2. An oauthlib.common.Request object.
        """
        try:
            request = self._create_request(uri, http_method, body, headers)
        except errors.OAuth1Error as e:  # noqa
            return False, None

        try:
            self._check_transport_security(request)
            self._check_mandatory_parameters(request)
        except errors.OAuth1Error as e:
            self.validation_error_message = e.description  # TOOPHER
            return False, request

        if not self.request_validator.validate_timestamp_and_nonce(
                request.client_key, request.timestamp, request.nonce, request):
            return False, request

        # The server SHOULD return a 401 (Unauthorized) status code when
        # receiving a request with invalid client credentials.
        # Note: This is postponed in order to avoid timing attacks, instead
        # a dummy client is assigned and used to maintain near constant
        # time request verification.
        #
        # Note that early exit would enable client enumeration
        valid_client = self.request_validator.validate_client_key(
            request.client_key, request)
        if not valid_client:
            request.client_key = self.request_validator.dummy_client

        valid_signature = self._check_signature(request)

        # We delay checking validity until the very end, using dummy values for
        # calculations and fetching secrets/keys to ensure the flow of every
        # request remains almost identical regardless of whether valid values
        # have been supplied. This ensures near constant time execution and
        # prevents malicious users from guessing sensitive information
        v = all((valid_client, valid_signature))
        if not v:
            log.info("[Failure] request verification failed.")
            log.info("Valid client: %s", valid_client)
            log.info("Valid signature: %s", valid_signature)

        if valid_client and not valid_signature:  # TOOPHER
            norm_params = signature.normalize_parameters(
                request.params)  # TOOPHER
            uri = signature.normalize_base_string_uri(request.uri)  # TOOPHER
            base_signing_string = signature.construct_base_string(
                request.http_method, uri, norm_params)  # TOOPHER
            self.validation_error_message = "Invalid signature. Expected signature base string: {0}".format(
                base_signing_string)  # TOOPHER
        return v, request
示例#13
0
async def post_grade(user_id, grade, sourcedid, outcomes_url):
    # TODO: extract this into a real library with real XML parsing
    # WARNING: You can use this only with data you trust! Beware, etc.
    post_xml = r"""
    <?xml version = "1.0" encoding = "UTF-8"?>
    <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
      <imsx_POXHeader>
        <imsx_POXRequestHeaderInfo>
          <imsx_version>V1.0</imsx_version>
          <imsx_messageIdentifier>999999123</imsx_messageIdentifier>
        </imsx_POXRequestHeaderInfo>
      </imsx_POXHeader>
      <imsx_POXBody>
        <replaceResultRequest>
          <resultRecord>
            <sourcedGUID>
              <sourcedId>{sourcedid}</sourcedId>
            </sourcedGUID>
            <result>
              <resultScore>
                <language>en</language>
                <textString>{grade}</textString>
              </resultScore>
            </result>
          </resultRecord>
        </replaceResultRequest>
      </imsx_POXBody>
    </imsx_POXEnvelopeRequest>
    """
    # Assumes these are read in from a config file in Jupyterhub
    consumer_key = os.environ['LTI_CONSUMER_KEY']
    consumer_secret = os.environ['LTI_CONSUMER_SECRET']
    sourcedid = "{}:{}".format(sourcedid, user_id)
    post_data = post_xml.format(grade=float(grade), sourcedid=sourcedid)

    # Yes, we do have to use sha1 :(
    body_hash_sha = sha1()
    body_hash_sha.update(post_data.encode('utf-8'))
    body_hash = base64.b64encode(body_hash_sha.digest()).decode('utf-8')
    args = {
        'oauth_body_hash': body_hash,
        'oauth_consumer_key': consumer_key,
        'oauth_timestamp': str(time.time()),
        'oauth_nonce': str(time.time())
    }

    base_string = signature.construct_base_string(
        'POST', signature.normalize_base_string_uri(outcomes_url),
        signature.normalize_parameters(
            signature.collect_parameters(body=args, headers={})))

    oauth_signature = signature.sign_hmac_sha1(base_string, consumer_secret,
                                               None)
    args['oauth_signature'] = oauth_signature

    headers = parameters.prepare_headers(
        args, headers={'Content-Type': 'application/xml'})

    async with async_timeout.timeout(10):
        async with aiohttp.ClientSession() as session:
            async with session.post(outcomes_url,
                                    data=post_data,
                                    headers=headers) as response:
                resp_text = await response.text()

                if response.status != 200:
                    raise GradePostException(response)

    response_tree = etree.fromstring(resp_text.encode('utf-8'))

    # XML and its namespaces. UBOOF!
    status_tree = response_tree.find(
        './/{http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0}imsx_statusInfo'
    )
    code_major = status_tree.find(
        '{http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0}imsx_codeMajor'
    ).text

    if code_major != 'success':
        raise GradePostException(response)