Home > Back-end >  Deserializer JSON objects containing JSON Web Signatures in Django. App Store Server Notifications r
Deserializer JSON objects containing JSON Web Signatures in Django. App Store Server Notifications r

Time:11-17

I have a python django rest application that I need it to be able to handle post request for App Store Server Notifications.

Thing is that v2 of the App Store Server Notifications payload is in JSON Web Signature (JWS) format, signed by the App Store. Which contains fields that in turn are also in JSON Web Signature (JWS) format, signed by the App Store. I know how to handle that using python-jose procedurally but I can't figure out how to fit the whole process within Django serializers in a graceful manner with as minimal hacking as possible.

The data could be something like:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub3RpZmljYXRpb25UeXBlIjoidHlwZSIsInN1YnR5cGUiOiJzdWJfVHlwZSIsIm5vdGlmaWNhdGlvblVVSUQiOiJzdHJpbmcgbm90aWZpY2F0aW9uVVVJRCIsImRhdGEiOnsiYXBwQXBwbGVJZCI6MTIzNCwiYnVuZGxlSWQiOiJhZmRzYXNkIiwiYnVuZGxlVmVyc2lvbiI6ImJ1bmRsZVZlcnNpb24iLCJlbnZpcm9ubWVudCI6ImVudmlyb25tZW50Iiwic2lnbmVkUmVuZXdhbEluZm8iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKaGRYUnZVbVZ1WlhkUWNtOWtkV04wU1dRaU9pSjBaWE4wSUhSdmJHVnRJaXdpWVhWMGIxSmxibVYzVTNSaGRIVnpJam94TENKbGVIQnBjbUYwYVc5dVNXNTBaVzUwSWpvMExDSm5jbUZqWlZCbGNtbHZaRVY0Y0dseVpYTkVZWFJsSWpveE5qTTJOVE0xTVRReExDSnBjMGx1UW1sc2JHbHVaMUpsZEhKNVVHVnlhVzlrSWpwMGNuVmxMQ0p2Wm1abGNrbGtaVzUwYVdacFpYSWlPaUowWlhOMElIUnZiR1Z0SWl3aWIyWm1aWEpVZVhCbElqb3hMQ0p2Y21sbmFXNWhiRlJ5WVc1ellXTjBhVzl1U1dRaU9pSjBaWE4wSUhSdmJHVnRJaXdpY0hKcFkyVkpibU55WldGelpWTjBZWFIxY3lJNk1Td2ljSEp2WkhWamRFbGtJam9pZEdWemRDQjBiMnhsYlNJc0luTnBaMjVsWkVSaGRHVWlPakUyTXpZMU16VXhOREY5LnYwWW9YQUd0MTFPeVBXUk8zV2xTZDRiSWVtcVV6Q0ZJbFdjd0ZwcEI5TmMiLCJzaWduZWRUcmFuc2FjdGlvbkluZm8iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKaGNIQkJZMk52ZFc1MFZHOXJaVzRpT2lKMFpYTjBJSFJ2YkdWdElpd2lZblZ1Wkd4bFNXUWlPaUp6WkdaellYTmtaaUlzSW1WNGNHbHlaWE5FWVhSbElqb3hOak0yTlRNMU1UUXhMQ0pwYmtGd2NFOTNibVZ5YzJocGNGUjVjR1VpT2lKMFpYTjBJSFJ2YkdWdElpd2lhWE5WY0dkeVlXUmxaQ0k2ZEhKMVpTd2liMlptWlhKSlpHVnVkR2xtYVdWeUlqb2lkR1Z6ZENCMGIyeGxiU0lzSW05bVptVnlWSGx3WlNJNk1UUTFMQ0p2Y21sbmFXNWhiRkIxY21Ob1lYTmxSR0YwWlNJNk1UWXpOalV6TlRFME1Td2liM0pwWjJsdVlXeFVjbUZ1YzJGamRHbHZia2xrSWpvaWRHVnpkQ0IwYjJ4bGJTSXNJbkJ5YjJSMVkzUkpaQ0k2SW5SbGMzUWdkRzlzWlcwaUxDSndkWEpqYUdGelpVUmhkR1VpT2pFMk16WTFNelV4TkRFc0luRjFZVzUwYVhSNUlqb3hORFVzSW5KbGRtOWpZWFJwYjI1RVlYUmxJam94TmpNMk5UTTFNVFF4TENKeVpYWnZZMkYwYVc5dVVtVmhjMjl1SWpveE5EVXNJbk5wWjI1bFpFUmhkR1VpT2pFMk16WTFNelV4TkRFc0luTjFZbk5qY21sd2RHbHZia2R5YjNWd1NXUmxiblJwWm1sbGNpSTZJblJsYzNRZ2RHOXNaVzBpTENKMGNtRnVjMkZqZEdsdmJrbGtJam9pZEdWemRDQjBiMnhsYlNJc0luUjVjR1VpT2lKMFpYTjBJSFJ2YkdWdElpd2lkMlZpVDNKa1pYSk1hVzVsU1hSbGJVbGtJam9pZEdWemRDQjBiMnhsYlNKOS5lbnlkTnVwd2txOTNYQ2dfeG5yYzNXTmtNNjM4NXpITnpoa0tqa3cyb3VrIn19.OgSJ4xE3r2Tw0Q4KcwPSD4YFo21uCLDgrKOtKOomijo

and then the part inbetween the dots decoded could look like

b'{"notificationType":"type","subtype":"sub_Type","notificationUUID":"string notificationUUID","data":{"appAppleId":1234,"bundleId":"afdsasd","bundleVersion":"bundleVersion","environment":"environment","signedRenewalInfo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRvUmVuZXdQcm9kdWN0SWQiOiJ0ZXN0IHRvbGVtIiwiYXV0b1JlbmV3U3RhdHVzIjoxLCJleHBpcmF0aW9uSW50ZW50Ijo0LCJncmFjZVBlcmlvZEV4cGlyZXNEYXRlIjoxNjM2NTM1MTQxLCJpc0luQmlsbGluZ1JldHJ5UGVyaW9kIjp0cnVlLCJvZmZlcklkZW50aWZpZXIiOiJ0ZXN0IHRvbGVtIiwib2ZmZXJUeXBlIjoxLCJvcmlnaW5hbFRyYW5zYWN0aW9uSWQiOiJ0ZXN0IHRvbGVtIiwicHJpY2VJbmNyZWFzZVN0YXR1cyI6MSwicHJvZHVjdElkIjoidGVzdCB0b2xlbSIsInNpZ25lZERhdGUiOjE2MzY1MzUxNDF9.v0YoXAGt11OyPWRO3WlSd4bIemqUzCFIlWcwFppB9Nc","signedTransactionInfo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBBY2NvdW50VG9rZW4iOiJ0ZXN0IHRvbGVtIiwiYnVuZGxlSWQiOiJzZGZzYXNkZiIsImV4cGlyZXNEYXRlIjoxNjM2NTM1MTQxLCJpbkFwcE93bmVyc2hpcFR5cGUiOiJ0ZXN0IHRvbGVtIiwiaXNVcGdyYWRlZCI6dHJ1ZSwib2ZmZXJJZGVudGlmaWVyIjoidGVzdCB0b2xlbSIsIm9mZmVyVHlwZSI6MTQ1LCJvcmlnaW5hbFB1cmNoYXNlRGF0ZSI6MTYzNjUzNTE0MSwib3JpZ2luYWxUcmFuc2FjdGlvbklkIjoidGVzdCB0b2xlbSIsInByb2R1Y3RJZCI6InRlc3QgdG9sZW0iLCJwdXJjaGFzZURhdGUiOjE2MzY1MzUxNDEsInF1YW50aXR5IjoxNDUsInJldm9jYXRpb25EYXRlIjoxNjM2NTM1MTQxLCJyZXZvY2F0aW9uUmVhc29uIjoxNDUsInNpZ25lZERhdGUiOjE2MzY1MzUxNDEsInN1YnNjcmlwdGlvbkdyb3VwSWRlbnRpZmllciI6InRlc3QgdG9sZW0iLCJ0cmFuc2FjdGlvbklkIjoidGVzdCB0b2xlbSIsInR5cGUiOiJ0ZXN0IHRvbGVtIiwid2ViT3JkZXJMaW5lSXRlbUlkIjoidGVzdCB0b2xlbSJ9.enydNupwkq93XCg_xnrc3WNkM6385zHNzhkKjkw2ouk"}}'

and then if the fields encoded in jws format are also decoded the same way mentioned aboce it is going to ultimately look like this:

{
"notificationType":"type",
"subtype":"sub_Type",
"notificationUUID":"string notificationUUID",
"data":
    {"appAppleId":1234,
     "bundleId":"afdsasd",
     "bundleVersion":"bundleVersion",
     "environment":"environment",
     "signedRenewalInfo":{
        "autoRenewProductId": "test tolem", 
        "autoRenewStatus": 1,
        "expirationIntent" : 4,
        "gracePeriodExpiresDate":  1636535141, 
        "isInBillingRetryPeriod": true,
        "offerIdentifier":  "test tolem", 
        "offerType": 1,
        "originalTransactionId": "test tolem", 
        "priceIncreaseStatus":  1,
        "productId": "test tolem",
        "signedDate": 1636535141,
        },
      "signedTransactionInfo":{
        "appAccountToken": "test tolem", 
        "bundleId": "sdfsasdf",
        "expiresDate" : 1636535141,
        "inAppOwnershipType":  "test tolem", 
        "isUpgraded": true,
        "offerIdentifier":  "test tolem", 
        "offerType": 145,
        "originalPurchaseDate": 1636535141,
        "originalTransactionId": "test tolem", 
        "productId":  "test tolem",
        "purchaseDate": 1636535141,
        "quantity": 145,
        "revocationDate": 1636535141,
        "revocationReason": 145,
        "signedDate": 1636535141,
        "subscriptionGroupIdentifier":  "test tolem", 
        "transactionId": "test tolem",
        "type":  "test tolem",
        "webOrderLineItemId": "test tolem" 
        }}}

Which is what I want to store into my database tables

Any help or idea is going to be greatly appriciated

CodePudding user response:

For anybody possibly dealing with the same issues this is how I handled the serialization it looked like it's working fine

 from .models import TransactionV2, RenewalInfoV2, 
    AppStoreDecodedPayloadV2, AppStoreDataV2
 from rest_framework import serializers
 from jose import jwt
 import base64
 import io
 from rest_framework.parsers import JSONParser
 import json
    
class PayloadField(serializers.Field):

    def to_internal_value(self, obj):
        content = obj.split('.')[1]
        jws_payload =  base64.b64decode(content)
        data = json.loads(jws_payload.decode())
        serializer = AppStoreDecodedPayloadSerializerV2(data = data)
        if serializer.is_valid():
            return serializer.validated_data

class SignedTransactionInfo(serializers.Field):

    def to_internal_value(self, obj):
        content = obj.split('.')[1]
        jws_payload =  base64.b64decode(content)
        data = json.loads(jws_payload.decode())
        serializer = TransactionV2Serializer(data = data)
        if serializer.is_valid():
            return serializer.validated_data


class SignedRenewalInfoField(serializers.Field):

    def to_internal_value(self, obj):
        content = obj.split('.')[1]
        algorithm = obj.split('.')[0]
        secret = obj.split('.')[2]

        jws_payload =  base64.b64decode(content)
        jws_payload_algo = base64.b64decode(algorithm)
       
        algo = json.loads(jws_payload_algo.decode())
        data = json.loads(jws_payload.decode())
        serializer = RenewSerializer(data = data)
        if serializer.is_valid():
            return serializer.validated_data


class AppStoreNotificationSerializerReuturnV2(serializers.Serializer):
    signedPayload =  PayloadField()

    def create(self, validated_data):
        app_store_decoded_payload_data = validated_data.pop('signedPayload')
        app_store_sub = AppStoreDecodedPayloadSerializerV2.create(app_store_decoded_payload_data)
        return app_store_sub

class AppStoreDecodedPayloadDataSerializer(serializers.Serializer):
    appAppleId = serializers.IntegerField()
    bundleId = serializers.CharField()
    bundleVersion = serializers.CharField()
    environment = serializers.CharField()
    signedRenewalInfo = SignedRenewalInfoField()
    signedTransactionInfo = SignedTransactionInfo()
    
    def create(**decoded_payload_data):
        renewall_data = decoded_payload_data.pop('signedRenewalInfo')
        transaction_data = decoded_payload_data.pop('signedTransactionInfo')
        app_store_data = AppStoreDataV2.objects.create(**decoded_payload_data)
        TransactionV2.objects.create(**transaction_data ,app_store_data = app_store_data)
        RenewalInfoV2.objects.create(**renewall_data, app_store_data = app_store_data)
        return app_store_data


class AppStoreDecodedPayloadSerializerV2(serializers.Serializer):
    notificationType = serializers.CharField()
    subtype = serializers.CharField()
    notificationUUID = serializers.CharField()
    data = AppStoreDecodedPayloadDataSerializer()
    
    def create(app_store_decoded_payload):
        app_store_decoded_payload_data = app_store_decoded_payload.pop('data')
        decoded_payload = AppStoreDecodedPayloadV2.objects.create(**app_store_decoded_payload)
        app_store_decoded_payload =AppStoreDecodedPayloadDataSerializer.create(**app_store_decoded_payload_data, app_store_decoded_payload = decoded_payload)
        return app_store_decoded_payload


class TransactionV2Serializer(serializers.ModelSerializer):

    class Meta:
        fields = '__all__'
        model = TransactionV2


class RenewSerializer(serializers.ModelSerializer):

    class Meta:
        fields = '__all__'
        model = RenewalInfoV2 
  • Related