Skip to content

Commit 384fe15

Browse files
committed
Add StudyplusKeychain
1 parent 15d4d26 commit 384fe15

File tree

3 files changed

+125
-63
lines changed

3 files changed

+125
-63
lines changed

Lib/StudyplusSDK.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
183FDAAF1E7544240085589F /* StudyplusError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183FDAAE1E7544240085589F /* StudyplusError.swift */; };
1616
183FDAB11E754DCB0085589F /* StudyplusAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183FDAB01E754DCB0085589F /* StudyplusAPI.swift */; };
1717
183FDAB31E7561B90085589F /* StudyplusRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183FDAB21E7561B90085589F /* StudyplusRecord.swift */; };
18+
65E3912925F8A74E00490547 /* StudyplusKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E3912825F8A74E00490547 /* StudyplusKeychain.swift */; };
1819
/* End PBXBuildFile section */
1920

2021
/* Begin PBXContainerItemProxy section */
@@ -39,6 +40,7 @@
3940
183FDAAE1E7544240085589F /* StudyplusError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StudyplusError.swift; sourceTree = "<group>"; };
4041
183FDAB01E754DCB0085589F /* StudyplusAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StudyplusAPI.swift; sourceTree = "<group>"; };
4142
183FDAB21E7561B90085589F /* StudyplusRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StudyplusRecord.swift; sourceTree = "<group>"; };
43+
65E3912825F8A74E00490547 /* StudyplusKeychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyplusKeychain.swift; sourceTree = "<group>"; };
4244
/* End PBXFileReference section */
4345

4446
/* Begin PBXFrameworksBuildPhase section */
@@ -105,6 +107,7 @@
105107
isa = PBXGroup;
106108
children = (
107109
183FDAB01E754DCB0085589F /* StudyplusAPI.swift */,
110+
65E3912825F8A74E00490547 /* StudyplusKeychain.swift */,
108111
);
109112
path = internal;
110113
sourceTree = "<group>";
@@ -229,6 +232,7 @@
229232
183FDAB11E754DCB0085589F /* StudyplusAPI.swift in Sources */,
230233
183FDAB31E7561B90085589F /* StudyplusRecord.swift in Sources */,
231234
183FDAAD1E753E430085589F /* StudyplusLoginDelegate.swift in Sources */,
235+
65E3912925F8A74E00490547 /* StudyplusKeychain.swift in Sources */,
232236
183FDAAF1E7544240085589F /* StudyplusError.swift in Sources */,
233237
183FDAAB1E75148D0085589F /* Studyplus.swift in Sources */,
234238
);

Lib/StudyplusSDK/Studyplus.swift

Lines changed: 12 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ final public class Studyplus {
6161
*/
6262
public weak var delegate: StudyplusLoginDelegate?
6363

64-
private let accessTokenStoreKey: String = "accessToken"
65-
private let usernameStoreKey: String = "username"
6664
private var serviceName: String {
6765
return "Studyplus_iOS_SDK_\(consumerKey)"
6866
}
@@ -82,7 +80,7 @@ final public class Studyplus {
8280
///
8381
/// Studyplusアプリとの連携を解除します。
8482
public func logout() {
85-
deleteKey()
83+
StudyplusKeychain.deleteAll(serviceName: serviceName)
8684
}
8785

8886
/// Returns to whether or not it is connected with Studyplus application.
@@ -100,22 +98,7 @@ final public class Studyplus {
10098
///
10199
/// - Returns: accessToken
102100
public func accessToken() -> String? {
103-
let query = [
104-
kSecClass: kSecClassGenericPassword,
105-
kSecAttrService: serviceName,
106-
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
107-
kSecMatchLimit: kSecMatchLimitOne,
108-
kSecReturnData: true,
109-
kSecAttrAccount: accessTokenStoreKey
110-
] as CFDictionary
111-
112-
var item: CFTypeRef?
113-
let status = SecItemCopyMatching(query, &item)
114-
guard status == errSecSuccess, let data = item as? Data else {
115-
return nil
116-
}
117-
118-
return String(data: data, encoding: .utf8)
101+
return StudyplusKeychain.accessToken(serviceName: serviceName)
119102
}
120103

121104
/// Username of Studyplus account. It is set when the auth or login is successful.
@@ -124,22 +107,7 @@ final public class Studyplus {
124107
///
125108
/// - Returns: username
126109
public func username() -> String? {
127-
let query = [
128-
kSecClass: kSecClassGenericPassword,
129-
kSecAttrService: serviceName,
130-
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
131-
kSecMatchLimit: kSecMatchLimitOne,
132-
kSecReturnData: true,
133-
kSecAttrAccount: usernameStoreKey
134-
] as CFDictionary
135-
136-
var item: CFTypeRef?
137-
let status = SecItemCopyMatching(query, &item)
138-
guard status == errSecSuccess, let data = item as? Data else {
139-
return nil
140-
}
141-
142-
return String(data: data, encoding: .utf8)
110+
return StudyplusKeychain.username(serviceName: serviceName)
143111
}
144112

145113
/// Studyplusに学習記録を投稿
@@ -195,26 +163,15 @@ final public class Studyplus {
195163
.trimmingCharacters(in: .whitespacesAndNewlines)
196164
.data(using: .utf8, allowLossyConversion: false)!
197165

198-
deleteKey()
199-
let statusAccessToken = SecItemAdd([
200-
kSecClass: kSecClassGenericPassword,
201-
kSecAttrService: serviceName,
202-
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
203-
kSecAttrAccount: accessTokenStoreKey,
204-
kSecValueData: accessToken
205-
] as CFDictionary, nil)
206-
let statusUsername = SecItemAdd([
207-
kSecClass: kSecClassGenericPassword,
208-
kSecAttrService: serviceName,
209-
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
210-
kSecAttrAccount: usernameStoreKey,
211-
kSecValueData: username
212-
] as CFDictionary, nil)
213-
214-
if statusAccessToken == noErr && statusUsername == noErr {
215-
delegate?.studyplusDidSuccessToLogin()
216-
} else {
217-
delegate?.studyplusDidFailToLogin(error: .keychainError)
166+
StudyplusKeychain.set(serviceName: serviceName,
167+
accessToken: accessToken,
168+
username: username) { result in
169+
switch result {
170+
case .failure(let error):
171+
self.delegate?.studyplusDidFailToLogin(error: error)
172+
case .success:
173+
self.delegate?.studyplusDidSuccessToLogin()
174+
}
218175
}
219176
case "fail":
220177
delegate?.studyplusDidFailToLogin(error: .fail)
@@ -316,12 +273,4 @@ final public class Studyplus {
316273

317274
return true
318275
}
319-
320-
private func deleteKey() {
321-
SecItemDelete([
322-
kSecClass: kSecClassGenericPassword,
323-
kSecAttrService: serviceName,
324-
kSecAttrSynchronizable: kSecAttrSynchronizableAny
325-
] as CFDictionary)
326-
}
327276
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//
2+
// StudyplusKeychain.swift
3+
// StudyplusSDK
4+
//
5+
// The MIT License (MIT)
6+
//
7+
// Copyright (c) 2021 Studyplus inc.
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
27+
import Foundation
28+
29+
internal struct StudyplusKeychain {
30+
private static let accessTokenStoreKey: String = "accessToken"
31+
private static let usernameStoreKey: String = "username"
32+
33+
static internal func accessToken(serviceName: String) -> String? {
34+
let query = [
35+
kSecClass: kSecClassGenericPassword,
36+
kSecAttrService: serviceName,
37+
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
38+
kSecMatchLimit: kSecMatchLimitOne,
39+
kSecReturnData: true,
40+
kSecAttrAccount: accessTokenStoreKey
41+
] as CFDictionary
42+
43+
var item: CFTypeRef?
44+
let status = SecItemCopyMatching(query, &item)
45+
guard status == errSecSuccess, let data = item as? Data else {
46+
return nil
47+
}
48+
49+
return String(data: data, encoding: .utf8)
50+
}
51+
52+
static internal func username(serviceName: String) -> String? {
53+
let query = [
54+
kSecClass: kSecClassGenericPassword,
55+
kSecAttrService: serviceName,
56+
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
57+
kSecMatchLimit: kSecMatchLimitOne,
58+
kSecReturnData: true,
59+
kSecAttrAccount: usernameStoreKey
60+
] as CFDictionary
61+
62+
var item: CFTypeRef?
63+
let status = SecItemCopyMatching(query, &item)
64+
guard status == errSecSuccess, let data = item as? Data else {
65+
return nil
66+
}
67+
68+
return String(data: data, encoding: .utf8)
69+
}
70+
71+
static internal func set(serviceName: String,
72+
accessToken: Data,
73+
username: Data,
74+
completion: (Result<Void, StudyplusLoginError>) -> Void) {
75+
// delete previous keys
76+
deleteAll(serviceName: serviceName)
77+
78+
// add new token and username
79+
let statusAccessToken = SecItemAdd([
80+
kSecClass: kSecClassGenericPassword,
81+
kSecAttrService: serviceName,
82+
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
83+
kSecAttrAccount: accessTokenStoreKey,
84+
kSecValueData: accessToken
85+
] as CFDictionary, nil)
86+
let statusUsername = SecItemAdd([
87+
kSecClass: kSecClassGenericPassword,
88+
kSecAttrService: serviceName,
89+
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
90+
kSecAttrAccount: usernameStoreKey,
91+
kSecValueData: username
92+
] as CFDictionary, nil)
93+
94+
if statusAccessToken != noErr || statusUsername != noErr {
95+
completion(.failure(.keychainError))
96+
return
97+
}
98+
99+
completion(.success(Void()))
100+
}
101+
102+
static func deleteAll(serviceName: String) {
103+
SecItemDelete([
104+
kSecClass: kSecClassGenericPassword,
105+
kSecAttrService: serviceName,
106+
kSecAttrSynchronizable: kSecAttrSynchronizableAny
107+
] as CFDictionary)
108+
}
109+
}

0 commit comments

Comments
 (0)