vendoo_v1.0/Pods/AeroGearOAuth2/AeroGearOAuth2/TrustedPersistantOAuth2Session.swift

334 lines
12 KiB
Swift

/*
* JBoss, Home of Professional Open Source.
* Copyright Red Hat, Inc., and individual contributors
*
* 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 Foundation
import Security
import UIKit
/**
The type of token to be saved in KeychainWrap:
- AccessToken: access token
- ExpirationDate: access token expiration date
- RefreshToken: refresh token
- RefreshExpirationDate: refresh token expiration date (used for Keycloak adapter only)
*/
public enum TokenType: String {
case AccessToken = "AccessToken"
case RefreshToken = "RefreshToken"
case ExpirationDate = "ExpirationDate"
case RefreshExpirationDate = "RefreshExpirationDate"
}
/**
A handy Keychain wrapper. It saves your OAuth2 tokens using WhenPasscodeSet ACL.
*/
public class KeychainWrap {
/**
The service id. By default set to apple bundle id.
*/
public var serviceIdentifier: String
/**
The group id is Keychain access group which is used for sharing keychain content accross multiple apps issued from same developer. By default there is no access group.
*/
public var groupId: String?
/**
Initialize KeychainWrapper setting default values.
:param: serviceId unique service, defulated to bundleId
:param: groupId used for SSO between app issued from same developer certificate.
*/
public init(serviceId: String? = NSBundle.mainBundle().bundleIdentifier, groupId: String? = nil) {
if serviceId == nil {
self.serviceIdentifier = "unkown"
} else {
self.serviceIdentifier = serviceId!
}
self.groupId = groupId
}
/**
Save tokens information in Keychain.
:param: key usually use accountId for oauth2 module, any unique string.
:param: tokenType type of token: access, refresh.
:param: value string value of the token.
*/
public func save(key: String, tokenType: TokenType, value: String) -> Bool {
let dataFromString: NSData? = value.dataUsingEncoding(NSUTF8StringEncoding)
if (dataFromString == nil) {
return false
}
// Instantiate a new default keychain query
let keychainQuery = NSMutableDictionary()
if let groupId = self.groupId {
keychainQuery[kSecAttrAccessGroup as String] = groupId
}
keychainQuery[kSecClass as String] = kSecClassGenericPassword
keychainQuery[kSecAttrService as String] = self.serviceIdentifier
keychainQuery[kSecAttrAccount as String] = key + "_" + tokenType.rawValue
keychainQuery[kSecAttrAccessible as String] = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
// Search for the keychain items
let statusSearch: OSStatus = SecItemCopyMatching(keychainQuery, nil)
// if found update
if (statusSearch == errSecSuccess) {
if (dataFromString != nil) {
let attributesToUpdate = NSMutableDictionary()
attributesToUpdate[kSecValueData as String] = dataFromString!
let statusUpdate: OSStatus = SecItemUpdate(keychainQuery, attributesToUpdate)
if (statusUpdate != errSecSuccess) {
print("tokens not updated")
return false
}
} else { // revoked token or newly installed app, clear KC
return self.resetKeychain()
}
} else if(statusSearch == errSecItemNotFound) { // if new, add
keychainQuery[kSecValueData as String] = dataFromString!
let statusAdd: OSStatus = SecItemAdd(keychainQuery, nil)
if(statusAdd != errSecSuccess) {
print("tokens not saved")
return false
}
} else { // error case
return false
}
return true
}
/**
Read tokens information in Keychain. If the entry is not found return nil.
:param: userAccount key of the keychain entry, usually accountId for oauth2 module.
:param: tokenType type of token: access, refresh.
*/
public func read(userAccount: String, tokenType: TokenType) -> String? {
let keychainQuery = NSMutableDictionary()
if let groupId = self.groupId {
keychainQuery[kSecAttrAccessGroup as String] = groupId
}
keychainQuery[kSecClass as String] = kSecClassGenericPassword
keychainQuery[kSecAttrService as String] = self.serviceIdentifier
keychainQuery[kSecAttrAccount as String] = userAccount + "_" + tokenType.rawValue
keychainQuery[kSecReturnData as String] = true
keychainQuery[kSecAttrAccessible as String] = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
var dataTypeRef: Unmanaged<AnyObject>?
// Search for the keychain items
let status: OSStatus = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(keychainQuery as CFDictionaryRef, UnsafeMutablePointer($0)) }
if (status == errSecItemNotFound) {
print("\(tokenType.rawValue) not found")
return nil
} else if (status != errSecSuccess) {
print("Error attempting to retrieve \(tokenType.rawValue) with error code \(status) ")
return nil
}
let opaque = dataTypeRef?.toOpaque()
var contentsOfKeychain: String?
if let op = opaque {
let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
// Convert the data retrieved from the keychain into a string
contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding) as? String
} else {
print("Nothing was retrieved from the keychain. Status code \(status)")
}
return contentsOfKeychain
}
/**
Clear all keychain entries. Note that Keychain can only be cleared progemmatically.
*/
public func resetKeychain() -> Bool {
return self.deleteAllKeysForSecClass(kSecClassGenericPassword) &&
self.deleteAllKeysForSecClass(kSecClassInternetPassword) &&
self.deleteAllKeysForSecClass(kSecClassCertificate) &&
self.deleteAllKeysForSecClass(kSecClassKey) &&
self.deleteAllKeysForSecClass(kSecClassIdentity)
}
func deleteAllKeysForSecClass(secClass: CFTypeRef) -> Bool {
let keychainQuery = NSMutableDictionary()
keychainQuery[kSecClass as String] = secClass
let result:OSStatus = SecItemDelete(keychainQuery)
if (result == errSecSuccess) {
return true
} else {
return false
}
}
}
/**
An OAuth2Session implementation to store OAuth2 metadata using the Keychain.
*/
public class TrustedPersistantOAuth2Session: OAuth2Session {
/**
The account id.
*/
public var accountId: String
/**
The access token's expiration date.
*/
public var accessTokenExpirationDate: NSDate? {
get {
let dateAsString = self.keychain.read(self.accountId, tokenType: .ExpirationDate)
if let unwrappedDate:String = dateAsString {
return NSDate(dateString: unwrappedDate)
} else {
return nil
}
}
set(value) {
if let unwrappedValue = value {
self.keychain.save(self.accountId, tokenType: .ExpirationDate, value: unwrappedValue.toString())
}
}
}
/**
The access token. The information is read securely from Keychain.
*/
public var accessToken: String? {
get {
return self.keychain.read(self.accountId, tokenType: .AccessToken)
}
set(value) {
if let unwrappedValue = value {
self.keychain.save(self.accountId, tokenType: .AccessToken, value: unwrappedValue)
}
}
}
/**
The refresh token. The information is read securely from Keychain.
*/
public var refreshToken: String? {
get {
return self.keychain.read(self.accountId, tokenType: .RefreshToken)
}
set(value) {
if let unwrappedValue = value {
self.keychain.save(self.accountId, tokenType: .RefreshToken, value: unwrappedValue)
}
}
}
/**
The refresh token's expiration date.
*/
public var refreshTokenExpirationDate: NSDate? {
get {
let dateAsString = self.keychain.read(self.accountId, tokenType: .RefreshExpirationDate)
if let unwrappedDate:String = dateAsString {
return NSDate(dateString: unwrappedDate)
} else {
return nil
}
}
set(value) {
if let unwrappedValue = value {
_ = self.keychain.save(self.accountId, tokenType: .RefreshExpirationDate, value: unwrappedValue.toString())
}
}
}
private let keychain: KeychainWrap
/**
Check validity of accessToken. return true if still valid, false when expired.
*/
public func tokenIsNotExpired() -> Bool {
return self.accessTokenExpirationDate != nil ? (self.accessTokenExpirationDate!.timeIntervalSinceDate(NSDate()) > 0) : true
}
/**
Check validity of refreshToken. return true if still valid, false when expired.
*/
public func refreshTokenIsNotExpired() -> Bool {
return self.refreshTokenExpirationDate != nil ? (self.refreshTokenExpirationDate!.timeIntervalSinceDate(NSDate()) > 0) : true
}
/**
Save in memory tokens information. Saving tokens allow you to refresh accesstoken transparently for the user without prompting for grant access.
*/
public func saveAccessToken(accessToken: String?, refreshToken: String?, accessTokenExpiration: String?, refreshTokenExpiration: String?) {
self.accessToken = accessToken
self.refreshToken = refreshToken
let now = NSDate()
if let inter = accessTokenExpiration?.doubleValue {
self.accessTokenExpirationDate = now.dateByAddingTimeInterval(inter)
}
if let inter = refreshTokenExpiration?.doubleValue {
self.refreshTokenExpirationDate = now.dateByAddingTimeInterval(inter)
}
}
/**
Clear all tokens. Method used when doing logout or revoke.
*/
public func clearTokens() {
self.accessToken = nil
self.refreshToken = nil
self.accessTokenExpirationDate = nil
self.refreshTokenExpirationDate = nil
}
/**
Initialize TrustedPersistantOAuth2Session using account id. Account id is the service id used for keychain storage.
:param: accountId uniqueId to identify the oauth2module
:param: groupId used for SSO between app issued from same developer certificate.
:param: accessToken optional parameter to initilaize the storage with initial values
:param: accessTokenExpirationDate optional parameter to initilaize the storage with initial values
:param: refreshToken optional parameter to initilaize the storage with initial values
:param: refreshTokenExpirationDate optional parameter to initilaize the storage with initial values
*/
public init(accountId: String,
groupId: String? = nil,
accessToken: String? = nil,
accessTokenExpirationDate: NSDate? = nil,
refreshToken: String? = nil,
refreshTokenExpirationDate: NSDate? = nil) {
self.accountId = accountId
if groupId != nil {
self.keychain = KeychainWrap(serviceId: groupId, groupId: groupId)
} else {
self.keychain = KeychainWrap()
}
self.accessToken = accessToken
self.refreshToken = refreshToken
self.accessTokenExpirationDate = accessTokenExpirationDate
self.refreshTokenExpirationDate = refreshTokenExpirationDate
}
}