import OpenSSL
import struct
import base64

from six.moves import http_client

from flask_restplus import abort

from werkzeug.exceptions import Unauthorized
from werkzeug.security import pbkdf2_hex

from driftbase.auth import get_provider_config
from driftbase.auth.util import fetch_url
from .authenticate import authenticate as base_authenticate


TRUSTED_ORGANIZATIONS = ["Apple Inc."]


def authenticate(auth_info):
    assert auth_info['provider'] == "gamecenter"
    provider_details = auth_info.get('provider_details')
    automatic_account_creation = auth_info.get("automatic_account_creation", True)
    identity_id = validate_gamecenter_token(provider_details)
    # The GameCenter user_id cannot be stored in plain text, so let's
    # give it one cycle of hashing.
    username = "gamecenter:" + pbkdf2_hex(identity_id, "staticsalt",
                                          iterations=1)
    return base_authenticate(username, "", automatic_account_creation)


def abort_unauthorized(description):
    """Raise an Unauthorized exception.
    """
    raise Unauthorized(description=description)


def validate_gamecenter_token(gc_token):
    """Validates Apple Game Center token 'gc_token'. If validation fails, an
    HTTPException:Unauthorized exception is raised.

    Returns a unique ID for this player.

    If configured, 'bundle_ids' is a list of app bundles id's, and the 'app_bundle_id' in
    the token must be one of the listed ones.


    Example:

    gc_token = {
        "public_key_url": "https://static.gc.apple.com/public-key/gc-prod-2.cer",
        "app_bundle_id": "com.directivegames.themachines.ios",
        "player_id": "G:1637867917",
        "timestamp": 1452128703383,
        "salt": "vPWarQ==",
        "signature": "ZuhbO8TqGKadYAZHsDd5NgTs/tmM8sIqhtxuUmxOlhmp8PUAofIYzdwaN...
    }

    validate_gamecenter_token(gc_token)

    """

    gamecenter_config = get_provider_config('gamecenter')
    if not gamecenter_config:
        abort(http_client.SERVICE_UNAVAILABLE, description="Game Center authentication not configured for current tenant")

    app_bundles = gamecenter_config.get("bundle_ids", None)
    return run_gamecenter_token_validation(gc_token=gc_token, app_bundles=app_bundles)


def run_gamecenter_token_validation(gc_token, app_bundles):
    token_desc = dict(gc_token)
    token_desc["signature"] = token_desc.get("signature", "?")[:10]
    error_title = 'Invalid Game Center token: %s' % token_desc

    # Verify required fields
    required_fields = [
        'app_bundle_id', 'player_id', 'public_key_url',
        'salt', 'signature', 'timestamp'
    ]

    missing_fields = list(set(required_fields) - set(gc_token.keys()))
    if missing_fields:
        abort_unauthorized(error_title + ". The token is missing required fields: %s." % ', '.join(missing_fields))

    # Verify that the token is issued to the appropriate app.
    if app_bundles and gc_token["app_bundle_id"] not in app_bundles:
        abort_unauthorized(error_title + ". 'app_bundle_id' not one of %s" % app_bundles)

    # Fetch public key, use cache if available.
    try:
        content = fetch_url(gc_token['public_key_url'], error_title)
    except Exception as e:
        abort_unauthorized(error_title + ". Can't fetch url '%s': %s" % (gc_token['public_key_url'], e))

    # Load certificate
    try:
        cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, content)
    except OpenSSL.crypto.Error as e:
        abort_unauthorized(error_title + ". Can't load certificate: %s" % str(e))

    # Verify that the key is issued to someone we trust and is not expired.
    org_name = cert.get_subject().organizationName
    if org_name not in TRUSTED_ORGANIZATIONS:
        abort_unauthorized(error_title + ". Certificate is issued to '%s' which is not one of %s." % (org_name, TRUSTED_ORGANIZATIONS))

    if cert.has_expired():
        abort_unauthorized(error_title + ". Certificate is expired, 'notAfter' is '%s'" % cert.get_notAfter())

    # Check signature
    salt_decoded = base64.b64decode(gc_token["salt"])
    payload = b""
    payload += gc_token["player_id"].encode('UTF-8')
    payload += gc_token["app_bundle_id"].encode('UTF-8')
    payload += struct.pack('>Q', int(gc_token["timestamp"]))
    payload += salt_decoded
    signature_decoded = base64.b64decode(gc_token["signature"])

    try:
        OpenSSL.crypto.verify(cert, signature_decoded, payload, 'sha256')
    except Exception as e:
        abort_unauthorized(error_title + ". Can't verify signature: %s" % str(e))

    return gc_token["player_id"]

@@ -70,16 +70,60 @@
 p_client
+%0Afrom six.moves.urllib.parse import urlparse
 %0A%0Afrom f
@@ -392,45 +392,175 @@
 e%0A%0A%0A
-TRUSTED_ORGANIZATIONS = %5B%22Apple Inc.%22
+# This stuff is just based on assumptions.%0ATRUSTED_ORGANIZATIONS = %5B%22Apple Inc.%22, %22Apple, Inc.%22%5D%0ATRUSTED_KEY_URL_HOSTS = %5B'sandbox.gc.apple.com', 'static.gc.apple.com'
 %5D%0A%0A%0A
@@ -3221,16 +3221,269 @@
 ndles)%0A%0A
+    # Verify that the certificate url is at Apple%0A    if urlparse(gc_token%5B'public_key_url'%5D).hostname not in TRUSTED_KEY_URL_HOSTS:%0A        abort_unauthorized(error_title + %22. Public key url points to unknown host: %25s%22 %25 (gc_token%5B'public_key_url'%5D))%0A%0A
     # Fe

Fix Game Center token validation