You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
12 KiB
306 lines
12 KiB
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright 2017 Realm Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
import XCTest
|
|
import RealmSwift
|
|
|
|
class SwiftPermissionsAPITests: SwiftSyncTestCase {
|
|
var userA: SyncUser!
|
|
var userB: SyncUser!
|
|
var userC: SyncUser!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
let baseName = UUID().uuidString
|
|
userA = try! synchronouslyLogInUser(for: .usernamePassword(username: baseName + "a", password: "a", register: true),
|
|
server: SwiftSyncTestCase.authServerURL())
|
|
userB = try! synchronouslyLogInUser(for: .usernamePassword(username: baseName + "b", password: "a", register: true),
|
|
server: SwiftSyncTestCase.authServerURL())
|
|
userC = try! synchronouslyLogInUser(for: .usernamePassword(username: baseName + "c", password: "a", register: true),
|
|
server: SwiftSyncTestCase.authServerURL())
|
|
}
|
|
|
|
override func tearDown() {
|
|
userA.logOut()
|
|
userB.logOut()
|
|
userC.logOut()
|
|
super.tearDown()
|
|
}
|
|
|
|
private func checkPermissionCount(results: SyncPermissionResults,
|
|
expected: Int,
|
|
file: StaticString = #file,
|
|
line: UInt = #line) {
|
|
let ex = expectation(description: "Checking permission count")
|
|
let token = results.observe { (change) in
|
|
if case let .error(theError) = change {
|
|
XCTFail("Notification returned error '\(theError)' when running test at \(file):\(line)")
|
|
return
|
|
}
|
|
if results.count == expected {
|
|
ex.fulfill()
|
|
}
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
token.invalidate()
|
|
}
|
|
|
|
private func get(permission: SyncPermission,
|
|
from results: SyncPermissionResults,
|
|
file: StaticString = #file,
|
|
line: UInt = #line) -> SyncPermission? {
|
|
let ex = expectation(description: "Retrieving permission")
|
|
var finalValue: SyncPermission?
|
|
let token = results.observe { (change) in
|
|
if case let .error(theError) = change {
|
|
XCTFail("Notification returned error '\(theError)' when running test at \(file):\(line)")
|
|
return
|
|
}
|
|
for result in results where result == permission {
|
|
finalValue = result
|
|
ex.fulfill()
|
|
return
|
|
}
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
token.invalidate()
|
|
return finalValue
|
|
}
|
|
|
|
/// Ensure the absence of a permission from a results after an elapsed time interval.
|
|
/// This method is intended to be used to check that a permission never becomes
|
|
/// present within a results to begin with.
|
|
private func ensureAbsence(of permission: SyncPermission,
|
|
from results: SyncPermissionResults,
|
|
after wait: Double = 0.5,
|
|
file: StaticString = #file,
|
|
line: UInt = #line) {
|
|
let ex = expectation(description: "Looking for permission")
|
|
var isPresent = false
|
|
let token = results.observe { (change) in
|
|
if case let .error(theError) = change {
|
|
XCTFail("Notification returned error '\(theError)' when running test at \(file):\(line)")
|
|
return
|
|
}
|
|
isPresent = results.contains(permission)
|
|
}
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + wait) {
|
|
ex.fulfill()
|
|
}
|
|
waitForExpectations(timeout: wait + 1.0, handler: nil)
|
|
token.invalidate()
|
|
XCTAssertFalse(isPresent, "Permission '\(permission)' was spuriously present (\(file):\(line))")
|
|
}
|
|
|
|
private func tildeSubstitutedURL(for url: URL, user: SyncUser) -> URL {
|
|
XCTAssertNotNil(user.identity)
|
|
let identity = user.identity!
|
|
return URL(string: url.absoluteString.replacingOccurrences(of: "~", with: identity))!
|
|
}
|
|
|
|
/// Setting a permission should work, and then that permission should be able to be retrieved.
|
|
func testSettingPermissions() {
|
|
// First, there should be no permissions.
|
|
let ex = expectation(description: "No permissions for newly created user.")
|
|
var results: SyncPermissionResults!
|
|
userB.retrievePermissions { (r, error) in
|
|
XCTAssertNil(error)
|
|
XCTAssertNotNil(r)
|
|
results = r
|
|
ex.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
checkPermissionCount(results: results, expected: 0)
|
|
|
|
// Open a Realm for user A.
|
|
let uuid = UUID().uuidString
|
|
let url = SwiftSyncTestCase.uniqueRealmURL(customName: uuid)
|
|
_ = try! synchronouslyOpenRealm(url: url, user: userA)
|
|
|
|
// Give user B read permissions to that Realm.
|
|
let p = SyncPermission(realmPath: tildeSubstitutedURL(for: url, user: userA).path,
|
|
identity: userB.identity!,
|
|
accessLevel: .read)
|
|
|
|
// Set the permission.
|
|
let ex2 = expectation(description: "Setting a permission should work.")
|
|
userA.apply(p) { (error) in
|
|
XCTAssertNil(error)
|
|
ex2.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
|
|
// Now retrieve the permissions again and make sure the new permission is properly set.
|
|
let ex3 = expectation(description: "One permission in results after setting the permission.")
|
|
userB.retrievePermissions { (r, error) in
|
|
XCTAssertNil(error)
|
|
XCTAssertNotNil(r)
|
|
results = r
|
|
ex3.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
// Expected permission: applies to user B, but for user A's Realm.
|
|
let finalValue = get(permission: p, from: results)
|
|
XCTAssertNotNil(finalValue, "Did not find the permission \(p)")
|
|
|
|
// Check getting permission by its index.
|
|
let index = results.index(of: p)
|
|
XCTAssertNotNil(index)
|
|
XCTAssertTrue(p == results[index!])
|
|
}
|
|
|
|
/// Observing permission changes should work.
|
|
func testObservingPermissions() {
|
|
// Get a reference to the permission results.
|
|
let ex = expectation(description: "Retrieve permission results.")
|
|
var results: SyncPermissionResults!
|
|
userB.retrievePermissions { (r, error) in
|
|
XCTAssertNil(error)
|
|
XCTAssertNotNil(r)
|
|
results = r
|
|
ex.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
|
|
// Open a Realm for user A.
|
|
let uuid = UUID().uuidString
|
|
let url = SwiftSyncTestCase.uniqueRealmURL(customName: uuid)
|
|
_ = try! synchronouslyOpenRealm(url: url, user: userA)
|
|
|
|
// Register notifications.
|
|
let noteEx = expectation(description: "Notification should fire")
|
|
let token = results.observe { (change) in
|
|
if case .error = change {
|
|
XCTFail("Should not return an error")
|
|
return
|
|
}
|
|
if results.count > 0 {
|
|
noteEx.fulfill()
|
|
}
|
|
}
|
|
|
|
// Give user B read permissions to that Realm.
|
|
let p = SyncPermission(realmPath: tildeSubstitutedURL(for: url, user: userA).path,
|
|
identity: userB.identity!,
|
|
accessLevel: .read)
|
|
|
|
// Set the permission.
|
|
let ex2 = expectation(description: "Setting a permission should work.")
|
|
userA.apply(p) { (error) in
|
|
XCTAssertNil(error)
|
|
ex2.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
|
|
// Wait for the notification to be fired.
|
|
wait(for: [noteEx], timeout: 2.0)
|
|
token.invalidate()
|
|
let finalValue = get(permission: p, from: results)
|
|
XCTAssertNotNil(finalValue, "Did not find the permission \(p)")
|
|
}
|
|
|
|
/// User should not be able to change a permission for a Realm they don't own.
|
|
func testSettingUnownedRealmPermission() {
|
|
// Open a Realm for user A.
|
|
let uuid = UUID().uuidString
|
|
let url = SwiftSyncTestCase.uniqueRealmURL(customName: uuid)
|
|
_ = try! synchronouslyOpenRealm(url: url, user: userA)
|
|
|
|
// Try to have user B give user C permissions to that Realm.
|
|
let p = SyncPermission(realmPath: url.path, identity: userC.identity!, accessLevel: .read)
|
|
|
|
// Attempt to set the permission.
|
|
let ex2 = expectation(description: "Setting an invalid permission should fail.")
|
|
userB.apply(p) { (error) in
|
|
XCTAssertNotNil(error)
|
|
ex2.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
|
|
// Now retrieve the permissions again and make sure the new permission was not set.
|
|
var results: SyncPermissionResults!
|
|
let ex3 = expectation(description: "Retrieving the results should work.")
|
|
userB.retrievePermissions { (r, error) in
|
|
XCTAssertNil(error)
|
|
XCTAssertNotNil(r)
|
|
results = r
|
|
ex3.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 2.0, handler: nil)
|
|
ensureAbsence(of: p, from: results)
|
|
}
|
|
|
|
// MARK: - Offer/response
|
|
|
|
func testPermissionOffer() {
|
|
_ = try! synchronouslyOpenRealm(url: realmURL, user: userA)
|
|
var token: String?
|
|
|
|
// Create an offer.
|
|
let ex = expectation(description: "A new permission offer will be processed by the server.")
|
|
userA.createOfferForRealm(at: realmURL, accessLevel: .write) { (t, error) in
|
|
XCTAssertNil(error)
|
|
XCTAssertNotNil(t)
|
|
token = t
|
|
ex.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 10.0, handler: nil)
|
|
XCTAssertGreaterThan(token!.lengthOfBytes(using: .utf8), 0)
|
|
}
|
|
|
|
func testPermissionOfferResponse() {
|
|
_ = try! synchronouslyOpenRealm(url: realmURL, user: userA)
|
|
var token: String?
|
|
|
|
// Create an offer.
|
|
let ex = expectation(description: "A new permission offer will be processed by the server.")
|
|
userA.createOfferForRealm(at: realmURL, accessLevel: .write) { (t, error) in
|
|
XCTAssertNil(error)
|
|
XCTAssertNotNil(t)
|
|
token = t
|
|
ex.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 10.0, handler: nil)
|
|
guard let theToken = token else {
|
|
XCTFail("We expected an offer token, but did not get one. Aborting the test.")
|
|
return
|
|
}
|
|
XCTAssertGreaterThan(theToken.lengthOfBytes(using: .utf8), 0)
|
|
|
|
// Accept the offer.
|
|
let ex2 = expectation(description: "A permission offer response will be processed by the server.")
|
|
var url: URL?
|
|
userB.acceptOffer(forToken: theToken) { (u, error) in
|
|
XCTAssertNil(error)
|
|
XCTAssertNotNil(u)
|
|
url = u
|
|
ex2.fulfill()
|
|
}
|
|
waitForExpectations(timeout: 10.0, handler: nil)
|
|
guard let theURL = url else {
|
|
XCTFail("We expected a Realm URL, but did not get one. Aborting the test.")
|
|
return
|
|
}
|
|
XCTAssertEqual(theURL.path, tildeSubstitutedURL(for: realmURL, user: userA).path)
|
|
do {
|
|
_ = try synchronouslyOpenRealm(url: theURL, user: userB)
|
|
} catch {
|
|
XCTFail("Was not able to successfully open the Realm with user B after accepting the offer.")
|
|
}
|
|
}
|
|
}
|
|
|