简体   繁体   中英

How to filter sensitive data server-side for object based on scenario

Scenario:

  • Class Example , subclassed from PFObject . Some sensitive properties are optional/can be nil.
  • User A creates examples locally on the client and saves it directly to the server (with ACL set to this user only)
  • User B should only see a subset of this data.

My approach:

  • create a cloud code function getExample for User B to call
  • getExample queries those examples, deletes all sensitive properties on the results (or recreates the object with only allowed properties) and returns those filtered objects.

Problem:

  • Whats the best approach to tell the parse engine that the return value is actually of type Example and make it parse it automatically? (call to server is via PFCloud.callFunction(inBackground: ...) )?

Thank you (also, any architecture advise highly appreciated).

Decoding backend JSON responses in Swift is almost trivial nowadays, with theDecodable protocol and the JSONDecoder class from the Foundation library.

Here's an example with an optional field:

import Foundation

let jsonString1 = """
{
    "stringField" : "stringValue",
    "intField" : 1,
    "floatField" : 1.1
}
"""

let jsonString2 = """
{
    "stringField" : "stringValue",
    "intField" : 1,
    "floatField" : 1.1,
    "optionalField" : "optionalValue"
}
"""

struct Response: Decodable {
    let stringField: String
    let intField: Int
    let floatField: Double
    let optionalField: String?
}

let response1 = try! JSONDecoder().decode(Response.self, from: jsonString1.data(using: .utf8)!)
// Response(stringField: "stringValue", intField: 1, floatField: 1.1, optionalField: nil)

let response2 = try! JSONDecoder().decode(Response.self, from: jsonString2.data(using: .utf8)!)
// Response(stringField: "stringValue", intField: 1, floatField: 1.1, optionalField: Optional("optionalValue"))

And one more example, with a nested object:

import Foundation

let jsonString = """
{
    "stringField" : "stringValue",
    "nestedObject" :
        {
            "intValue" : 2
        }
}
"""

struct Response: Decodable {
    
    struct NestedType: Decodable {
        let intValue: Int
    }
    
    let stringField: String
    let nestedObject: NestedType
    
}

let response = try! JSONDecoder().decode(Response.self, from: jsonString.data(using: .utf8)!)
// Response(stringField: "stringValue", nestedObject: __lldb_expr_10.Response.NestedType(intValue: 2))

An example with a date:

import Foundation

let jsonString = """
{
    "date" : "2021-02-04 09:38:33.000"
}
"""

struct Response: Decodable {
    let date: Date
}

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let response = try! decoder.decode(Response.self, from: jsonString.data(using: .utf8)!)
// Response(date: 2021-02-04 09:38:33 +0000)

Find out more here .

UPDATE

After discussion it became clear that the problem is that the object to parse is a dictionary containing objects that cannot be represented in JSON natively (eg Data / NSData ). In this case it's possible to convert them into something representable (eg for dates it can be a formatted String ). Here's an example involving NSDate and JSONSerialization :

import Foundation

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

let jsonDict1 = ["stringField" : "stringValue"]
let jsonData1 = try JSONSerialization.data(withJSONObject: jsonDict1)

let date = NSDate()
let jsonDict2: [String : Any] = ["stringField" : "stringValue",
                                 "optionalDate" : dateFormatter.string(from: date as Date)]
let jsonData2 = try JSONSerialization.data(withJSONObject: jsonDict2)

struct Response: Decodable {
  let stringField: String
  let optionalDate: Date?
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let response1 = try! decoder.decode(Response.self, from: jsonData1)
// Response(stringField: "stringValue", optionalDate: nil)

let response2 = try! decoder.decode(Response.self, from: jsonData2)
// Response(stringField: "stringValue", optionalDate: Optional(2022-02-21 13:48:48 +0000))

After some back and forth I found a good solution that ensures automated parsing of redacted PFObject subclasses. The trick is to add the properties __type and className to the return value so that the objective-c Parse SDK can recognize the type automatically

Cloud Code (simplified):

Parse.Cloud.define("getExamples", async (request) => {
    let query = new Parse.Query(ExampleClass);
    query.limit(50);
    let results = await query.find({useMasterKey:true});

    let returnResults = [];

    _.each(results, function(item) {
        // make a JSON copy
        let returnItem = item.toJSON();

        // delete sensitive properties based on internal rules
        if(thisSpecificUserShouldnotHaveAccess) {
          delete returnItem["maybeSensitive"];
        }

        // Mark as valid PFObject (important!)
        returnItem["__type"] = "Object";
        returnItem["className"] = "Example";

        returnResults.push(returnItem);
    });

    return returnResults;
});

Swift Class Code (simplified):

class Example: PFObject {
   public static func parseClassName() -> String { "Example" }
   
   @NSManaged public var title: String?
    
   @NSManaged public var maybeSensitive: String? // will be nil based on server logic    
}

Swift Retrieve Code (simplified):

    func callFunction<T: PFObject>(name: String, parameters: [String: Any]? = nil) async throws -> [T] {
        return try await withCheckedThrowingContinuation { continuation in
            PFCloud.callFunction(inBackground: name, withParameters: parameters) { (result, error) in
                do {
                    if let error = error {
                        throw error
                    }
                    if let parsed = result as? [T] {
                        continuation.resume(returning: parsed)
                        return
                    }
                    
                    if let parsed = result as? T {
                        continuation.resume(returning: [parsed])
                        return
                    }
                    throw Error.notAPFObject // defined somewhere else
                } catch {
                    continuation.resume(with: .failure(error))
                }
            }
        }
    }

Call to server:

let examples: [Example] = try await callFunction("getExamples")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM