Skip to content

Commit bc6ea18

Browse files
authored
feat: Add support of Editable Shared Link (#861)
1 parent 8ad475c commit bc6ea18

File tree

15 files changed

+297
-14
lines changed

15 files changed

+297
-14
lines changed

BoxSDK.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
05610A9927107079009F92CC /* SignRequestCreateParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05610A9827107079009F92CC /* SignRequestCreateParameters.swift */; };
4040
05610A9B271099A7009F92CC /* SignRequestPrefillTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05610A9A271099A7009F92CC /* SignRequestPrefillTag.swift */; };
4141
05610A9D27109BFB009F92CC /* SignRequestSignerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05610A9C27109BFB009F92CC /* SignRequestSignerInput.swift */; };
42+
056B628B2860C16F008E9418 /* SharedLinkDataSpecs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 056B628A2860C16F008E9418 /* SharedLinkDataSpecs.swift */; };
4243
0579F6F727577DAD00473A3C /* Nimble.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 05EFAB1F26F0F01100DF1830 /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4344
0579F6F827577DAD00473A3C /* Quick.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 05EFAB2026F0F01100DF1830 /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4445
0579F70027577FD200473A3C /* FolderModuleIntegrationSpecs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0579F6FF27577FD200473A3C /* FolderModuleIntegrationSpecs.swift */; };
@@ -632,6 +633,7 @@
632633
05610A9827107079009F92CC /* SignRequestCreateParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRequestCreateParameters.swift; sourceTree = "<group>"; };
633634
05610A9A271099A7009F92CC /* SignRequestPrefillTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRequestPrefillTag.swift; sourceTree = "<group>"; };
634635
05610A9C27109BFB009F92CC /* SignRequestSignerInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRequestSignerInput.swift; sourceTree = "<group>"; };
636+
056B628A2860C16F008E9418 /* SharedLinkDataSpecs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedLinkDataSpecs.swift; sourceTree = "<group>"; };
635637
056FE12726EF6F6800098F00 /* Quick.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Quick.xcframework; path = Carthage/Build/Quick.xcframework; sourceTree = "<group>"; };
636638
056FE12826EF6F6800098F00 /* OHHTTPStubs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OHHTTPStubs.xcframework; path = Carthage/Build/OHHTTPStubs.xcframework; sourceTree = "<group>"; };
637639
056FE12926EF6F6800098F00 /* Nimble.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Nimble.xcframework; path = Carthage/Build/Nimble.xcframework; sourceTree = "<group>"; };
@@ -1241,6 +1243,7 @@
12411243
isa = PBXGroup;
12421244
children = (
12431245
05EE8150271869B4006A2329 /* SignRequestCreateRequestSpecs.swift */,
1246+
056B628A2860C16F008E9418 /* SharedLinkDataSpecs.swift */,
12441247
);
12451248
path = BodyData;
12461249
sourceTree = "<group>";
@@ -3016,6 +3019,7 @@
30163019
05EE814827186336006A2329 /* SignRequestSignerRoleSpecs.swift in Sources */,
30173020
97F04DA122B84E540034B9A3 /* UserSpecs.swift in Sources */,
30183021
05A58A4726FB487500AB309C /* EventTypeSpecs.swift in Sources */,
3022+
056B628B2860C16F008E9418 /* SharedLinkDataSpecs.swift in Sources */,
30193023
05F59F0626FC740100D9A539 /* FileRepresentationSpecs.swift in Sources */,
30203024
05F59F0E26FC984900D9A539 /* AuthenticationSessionSpecs.swift in Sources */,
30213025
F929D63C2331B06B0039E452 /* LegalHoldsModuleSpecs.swift in Sources */,

IntegrationTests/Async/FolderModuleAsyncIntegrationTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ class FolderModuleAsyncIntegrationTests: BaseAsyncIntegrationTests {
157157

158158
XCTAssertEqual(createdSharedLink.access, .open)
159159
XCTAssertEqual(createdSharedLink.permissions?.canDownload, true)
160+
XCTAssertEqual(createdSharedLink.permissions?.canPreview, true)
161+
XCTAssertEqual(createdSharedLink.permissions?.canEdit, false)
160162
XCTAssertEqual(createdSharedLink.isPasswordEnabled, true)
161163
XCTAssertEqual(createdSharedLink.vanityName, "iOS-SDK-Folder-VanityName")
162164

@@ -165,6 +167,8 @@ class FolderModuleAsyncIntegrationTests: BaseAsyncIntegrationTests {
165167

166168
XCTAssertEqual(fetchedSharedLink.access, .open)
167169
XCTAssertEqual(fetchedSharedLink.permissions?.canDownload, true)
170+
XCTAssertEqual(fetchedSharedLink.permissions?.canPreview, true)
171+
XCTAssertEqual(fetchedSharedLink.permissions?.canEdit, false)
168172
XCTAssertEqual(fetchedSharedLink.isPasswordEnabled, true)
169173
XCTAssertEqual(fetchedSharedLink.vanityName, "iOS-SDK-Folder-VanityName")
170174

IntegrationTests/FileModuleIntegrationSpecs.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,12 +699,15 @@ class FileModuleIntegrationSpecs: BaseIntegrationSpecs {
699699
self.client.files.setSharedLink(
700700
forFile: file.id,
701701
access: .open,
702-
canDownload: true
702+
canDownload: true,
703+
canEdit: true
703704
) { result in
704705
switch result {
705706
case let .success(sharedLink):
706707
expect(sharedLink.access).to(equal(.open))
707708
expect(sharedLink.permissions?.canDownload).to(equal(true))
709+
expect(sharedLink.permissions?.canPreview).to(equal(true))
710+
expect(sharedLink.permissions?.canEdit).to(equal(true))
708711
expect(sharedLink.isPasswordEnabled).to(equal(false))
709712
expect(sharedLink.vanityName).to(beNil())
710713
case let .failure(error):
@@ -728,6 +731,8 @@ class FileModuleIntegrationSpecs: BaseIntegrationSpecs {
728731
case let .success(sharedLink):
729732
expect(sharedLink.access).to(equal(.open))
730733
expect(sharedLink.permissions?.canDownload).to(equal(true))
734+
expect(sharedLink.permissions?.canPreview).to(equal(true))
735+
expect(sharedLink.permissions?.canEdit).to(equal(true))
731736
expect(sharedLink.isPasswordEnabled).to(equal(true))
732737
expect(sharedLink.vanityName).to(equal("iOS-SDK-File-VanityName"))
733738
case let .failure(error):
@@ -745,6 +750,8 @@ class FileModuleIntegrationSpecs: BaseIntegrationSpecs {
745750
case let .success(sharedLink):
746751
expect(sharedLink.access).to(equal(.open))
747752
expect(sharedLink.permissions?.canDownload).to(equal(true))
753+
expect(sharedLink.permissions?.canPreview).to(equal(true))
754+
expect(sharedLink.permissions?.canEdit).to(equal(true))
748755
expect(sharedLink.isPasswordEnabled).to(equal(true))
749756
expect(sharedLink.vanityName).to(equal("iOS-SDK-File-VanityName"))
750757
case let .failure(error):

IntegrationTests/FolderModuleIntegrationSpecs.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,8 @@ class FolderModuleIntegrationSpecs: BaseIntegrationSpecs {
411411
case let .success(sharedLink):
412412
expect(sharedLink.access).to(equal(.open))
413413
expect(sharedLink.permissions?.canDownload).to(equal(true))
414+
expect(sharedLink.permissions?.canPreview).to(equal(true))
415+
expect(sharedLink.permissions?.canEdit).to(equal(false))
414416
expect(sharedLink.isPasswordEnabled).to(equal(true))
415417
expect(sharedLink.vanityName).to(equal("iOS-SDK-Folder-VanityName"))
416418
case let .failure(error):
@@ -428,6 +430,8 @@ class FolderModuleIntegrationSpecs: BaseIntegrationSpecs {
428430
case let .success(sharedLink):
429431
expect(sharedLink.access).to(equal(.open))
430432
expect(sharedLink.permissions?.canDownload).to(equal(true))
433+
expect(sharedLink.permissions?.canPreview).to(equal(true))
434+
expect(sharedLink.permissions?.canEdit).to(equal(false))
431435
expect(sharedLink.isPasswordEnabled).to(equal(true))
432436
expect(sharedLink.vanityName).to(equal("iOS-SDK-Folder-VanityName"))
433437
case let .failure(error):

Sources/Modules/FilesModule.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,7 @@ public class FilesModule {
12571257
/// - access: The level of access. If you omit this field then the access level will be set to the default access level specified by the enterprise admin
12581258
/// - password: The password required to access the shared link. Set to .empty to delete the password
12591259
/// - canDownload: Whether the shared link allows downloads
1260+
/// - canEdit: Whether the shared link allows editing
12601261
/// - completion: Returns a standard SharedLink object or an error
12611262
public func setSharedLink(
12621263
forFile fileId: String,
@@ -1265,6 +1266,7 @@ public class FilesModule {
12651266
access: SharedLinkAccess? = nil,
12661267
password: NullableParameter<String>? = nil,
12671268
canDownload: Bool? = nil,
1269+
canEdit: Bool? = nil,
12681270
completion: @escaping Callback<SharedLink>
12691271
) {
12701272
update(
@@ -1274,7 +1276,8 @@ public class FilesModule {
12741276
password: password,
12751277
unsharedAt: unsharedAt,
12761278
vanityName: vanityName,
1277-
canDownload: canDownload
1279+
canDownload: canDownload,
1280+
canEdit: canEdit
12781281
)),
12791282
fields: ["shared_link"]
12801283
) { result in

Sources/Modules/FoldersModule.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ public class FoldersModule {
255255
case .null:
256256
body["shared_link"] = NSNull()
257257
case let .value(sharedLinkValue):
258-
body["shared_link"] = sharedLinkValue.bodyDict
258+
body["shared_link"] = sharedLinkValue.copyWithoutPermissions(["can_edit"]).bodyDict
259259
}
260260
}
261261

Sources/Requests/BodyData/SharedLinkData.swift

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,48 @@ public struct SharedLinkData: Encodable {
2828
/// - vanityName: The custom vanity name to use in the shared link URL.
2929
/// It should be between 12 and 30 characters. This field can contains only letters, numbers, and hyphens.
3030
/// - canDownload: Permission specifying whether user can download from the shared link.
31+
/// - canEdit: Permission specifying whether user can edit from the shared link.
32+
/// This value can only be true if `canDownload` is also true and if the item has a type of file.
3133
public init(
3234
access: SharedLinkAccess? = nil,
3335
password: NullableParameter<String>? = nil,
3436
unsharedAt: NullableParameter<Date>? = nil,
3537
vanityName: NullableParameter<String>? = nil,
36-
canDownload: Bool? = nil
38+
canDownload: Bool? = nil,
39+
canEdit: Bool? = nil
3740
) {
3841
self.access = access
3942
self.password = password
4043
self.unsharedAt = unsharedAt
4144
self.vanityName = vanityName
4245

43-
if let canDownload = canDownload {
44-
permissions = ["can_download": canDownload]
46+
var permissions = [String: Bool]()
47+
permissions["can_download"] = canDownload
48+
permissions["can_edit"] = canEdit
49+
self.permissions = permissions.isEmpty ? nil : permissions
50+
}
51+
52+
/// Create a copy of current SharedLinkData object with excluded passed permissions
53+
/// - Parameters:
54+
/// - permissions: Array of permissions that should be excluded during copying
55+
/// - Returns: Returns copied SharedLinkData object.
56+
internal func copyWithoutPermissions(_ permissions: [String]?) -> SharedLinkData {
57+
guard let permissions = permissions, let sourcePermissions = self.permissions else {
58+
return self
4559
}
46-
else {
47-
permissions = nil
60+
61+
var destinationPermissions = sourcePermissions
62+
for item in permissions {
63+
destinationPermissions[item] = nil
4864
}
65+
66+
return SharedLinkData(
67+
access: access,
68+
password: password,
69+
unsharedAt: unsharedAt,
70+
vanityName: vanityName,
71+
canDownload: destinationPermissions.first { $0.key == "can_download" }?.value,
72+
canEdit: destinationPermissions.first { $0.key == "can_edit" }?.value
73+
)
4974
}
5075
}

Sources/Requests/QueryParameters/NullableParameter.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ extension NullableParameter: Encodable where T: Encodable {
3030
}
3131
}
3232
}
33+
34+
/// Extension for Equatable conformance
35+
extension NullableParameter: Equatable where T: Equatable {}

Sources/Responses/SharedLink.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public class SharedLink: BoxModel {
4747
public let canPreview: Bool?
4848
/// Whether the shared link allows downloads. For shared links on folders, this also applies to any items in the folder.
4949
public let canDownload: Bool?
50+
/// Whether the shared link allows editing of files.
51+
public let canEdit: Bool?
5052
}
5153

5254
// MARK: - BoxModel

Tests/Modules/FilesModuleSpecs.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,75 @@ class FilesModuleSpecs: QuickSpec {
17661766
}
17671767

17681768
describe("setSharedLink()") {
1769+
context("updating shared link with setting canDownload and canEdit to true") {
1770+
beforeEach {
1771+
stub(
1772+
condition: isHost("api.box.com") &&
1773+
isPath("/2.0/files/5000948880") &&
1774+
isMethodPUT() &&
1775+
containsQueryParams(["fields": "shared_link"]) &&
1776+
hasJsonBody(["shared_link": ["access": "open", "permissions": ["can_download": true, "can_edit": true]]])
1777+
) { _ in
1778+
OHHTTPStubsResponse(
1779+
fileAtPath: OHPathForFile("GetFileSharedLink.json", type(of: self))!,
1780+
statusCode: 200, headers: ["Content-Type": "application/json"]
1781+
)
1782+
}
1783+
}
1784+
it("should update a shared link on a file", closure: {
1785+
waitUntil(timeout: .seconds(10)) { done in
1786+
self.sut.files.setSharedLink(forFile: "5000948880", access: .open, canDownload: true, canEdit: true) { result in
1787+
switch result {
1788+
case let .success(sharedLink):
1789+
expect(sharedLink.access).to(equal(.open))
1790+
expect(sharedLink.previewCount).to(equal(0))
1791+
expect(sharedLink.downloadCount).to(equal(0))
1792+
expect(sharedLink.downloadURL).toNot(beNil())
1793+
expect(sharedLink.permissions?.canDownload).to(equal(true))
1794+
expect(sharedLink.permissions?.canEdit).to(equal(true))
1795+
case let .failure(error):
1796+
fail("Expected call to setSharedLink to suceeded, but instead got \(error)")
1797+
}
1798+
done()
1799+
}
1800+
}
1801+
})
1802+
}
1803+
1804+
context("updating shared link with setting canDownload to true and canEdit to false") {
1805+
beforeEach {
1806+
stub(
1807+
condition: isHost("api.box.com") &&
1808+
isPath("/2.0/files/5000948880") &&
1809+
isMethodPUT() &&
1810+
containsQueryParams(["fields": "shared_link"]) &&
1811+
hasJsonBody(["shared_link": ["access": "open", "permissions": ["can_download": true, "can_edit": false]]])
1812+
) { _ in
1813+
OHHTTPStubsResponse(
1814+
fileAtPath: OHPathForFile("GetFileSharedLink.json", type(of: self))!,
1815+
statusCode: 200, headers: ["Content-Type": "application/json"]
1816+
)
1817+
}
1818+
}
1819+
it("should update a shared link on a file", closure: {
1820+
waitUntil(timeout: .seconds(10)) { done in
1821+
self.sut.files.setSharedLink(forFile: "5000948880", access: .open, canDownload: true, canEdit: false) { result in
1822+
switch result {
1823+
case let .success(sharedLink):
1824+
expect(sharedLink.access).to(equal(.open))
1825+
expect(sharedLink.previewCount).to(equal(0))
1826+
expect(sharedLink.downloadCount).to(equal(0))
1827+
expect(sharedLink.downloadURL).toNot(beNil())
1828+
expect(sharedLink.permissions?.canDownload).to(equal(true))
1829+
case let .failure(error):
1830+
fail("Expected call to setSharedLink to suceeded, but instead got \(error)")
1831+
}
1832+
done()
1833+
}
1834+
}
1835+
})
1836+
}
1837+
17691838
context("updating shared link and setting password to new value") {
17701839
beforeEach {
17711840
stub(

0 commit comments

Comments
 (0)