Gather identifying information about a user via an authorized token.

    :Parameters:
        mw_uri : `str`
            The base URI of the MediaWiki installation.  Note that the URI
            should end in ``"index.php"``.
        consumer_token : :class:`~mwoauth.ConsumerToken`
            A token representing you, the consumer.
        access_token : :class:`~mwoauth.AccessToken`
            A token representing an authorized user.  Obtained from
            `complete()`
        leeway : `int` | `float`
            The number of seconds of leeway to account for when examining a
            tokens "issued at" timestamp.

    :Returns:
        A dictionary containing identity information.

def identify(mw_uri, consumer_token, access_token, leeway=10.0,
             user_agent=defaults.USER_AGENT):
    """
    Gather identifying information about a user via an authorized token.

    :Parameters:
        mw_uri : `str`
            The base URI of the MediaWiki installation.  Note that the URI
            should end in ``"index.php"``.
        consumer_token : :class:`~mwoauth.ConsumerToken`
            A token representing you, the consumer.
        access_token : :class:`~mwoauth.AccessToken`
            A token representing an authorized user.  Obtained from
            `complete()`
        leeway : `int` | `float`
            The number of seconds of leeway to account for when examining a
            tokens "issued at" timestamp.

    :Returns:
        A dictionary containing identity information.
    """

    # Construct an OAuth auth
    auth = OAuth1(consumer_token.key,
                  client_secret=consumer_token.secret,
                  resource_owner_key=access_token.key,
                  resource_owner_secret=access_token.secret)

    # Request the identity using auth
    r = requests.post(url=mw_uri,
                      params={'title': "Special:OAuth/identify"},
                      auth=auth,
                      headers={'User-Agent': user_agent})

    # Special:OAuth/identify unhelpfully returns 200 status even when there is
    # an error in the API call. Check for error messages manually.
    if r.content.startswith(b'{'):
        try:
            resp = r.json()
            if 'error' in resp:
                raise OAuthException(
                    "A MediaWiki API error occurred: {0}".format(resp['message']))
        except ValueError:
            raise OAuthException(
                "An error occurred while trying to read json " +
                "content: {0}".format(e))


    # Decode json & stuff
    try:
        identity = jwt.decode(r.content, consumer_token.secret,
                              audience=consumer_token.key,
                              algorithms=["HS256"],
                              leeway=leeway)
    except jwt.InvalidTokenError as e:
        raise OAuthException(
            "An error occurred while trying to read json " +
            "content: {0}".format(e))

    # Verify the issuer is who we expect (server sends $wgCanonicalServer)
    issuer = urlparse(identity['iss']).netloc
    expected_domain = urlparse(mw_uri).netloc
    if not issuer == expected_domain:
        raise OAuthException(
            "Unexpected issuer " +
            "{0}, expected {1}".format(issuer, expected_domain))

    # Check that the identity was issued in the past.
    now = time.time()
    issued_at = float(identity['iat'])
    if not now >= (issued_at - leeway):
        raise OAuthException(
            "Identity issued {0} ".format(issued_at - now) +
            "seconds in the future!")

    # Verify that the nonce matches our request one,
    # to avoid a replay attack
    authorization_header = force_unicode(r.request.headers['Authorization'])
    request_nonce = re.search(r'oauth_nonce="(.*?)"',
                              authorization_header).group(1)
    if identity['nonce'] != request_nonce:
        raise OAuthException(
            'Replay attack detected: {0} != {1}'.format(
                identity['nonce'], request_nonce))

    return identity