如何根据场景过滤对象的敏感数据服务器端

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

场景:

我的做法:

问题:

谢谢(另外,非常感谢任何架构建议)。

在 Swift 中解码后端 JSON 响应现在几乎是微不足道的,使用 Decodable protocol and the JSONDecoder class from the Foundation 库。

这是一个带有可选字段的示例:

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"))

再举一个嵌套对象的例子:

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))

带日期的例子:

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)

了解更多 here

更新

经过讨论,很明显问题是要解析的对象是一个字典,其中包含无法在 JSON 中本地表示的对象(例如 Data/NSData)。在这种情况下,可以将它们转换成可表示的东西(例如,对于日期,它可以是格式化的 String)。这是一个涉及 NSDateJSONSerialization 的示例:

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))

经过一番折腾,我找到了一个很好的解决方案,可以确保自动解析已编辑的 PFObject 子类。诀窍是将属性 __typeclassName 添加到 return 值,以便 objective-c Parse SDK 可以自动识别类型

云代码(简体):

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 代码(简体):

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 检索代码(简体):

    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))
                }
            }
        }
    }

调用服务器:

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