Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,55 @@ class OSIdentityModel: OSModel {
return internalGetAlias(OS_EXTERNAL_ID)
}

// All access to aliases should go through helper methods with locking
// All access to aliases and jwtBearerToken must go through the lock
var aliases: [String: String] = [:]
private let aliasesLock = NSRecursiveLock()
private let lock = NSRecursiveLock()

// MARK: - JWT

private var jwtBearerTokenLocked: String? // only read/write under self.lock
public var jwtBearerToken: String? {
didSet {
guard jwtBearerToken != oldValue else {
return
get {
lock.withLock { jwtBearerTokenLocked }
}
set {
// Lock only the storage write. The change notifier fires synchronously
// to listeners that may take other locks
let changed = lock.withLock {
guard newValue != jwtBearerTokenLocked else { return false }
jwtBearerTokenLocked = newValue
return true
}
if changed {
self.set(property: OS_JWT_BEARER_TOKEN, newValue: newValue)
}
self.set(property: OS_JWT_BEARER_TOKEN, newValue: jwtBearerToken)
}
}

func isJwtValid() -> Bool {
return jwtBearerToken != nil && jwtBearerToken != "" && jwtBearerToken != OS_JWT_TOKEN_INVALID
/// Returns the bearer token if it is valid, otherwise nil, snapshots once
func getValidJwt() -> String? {
let token = jwtBearerToken
guard let token = token, !token.isEmpty, token != OS_JWT_TOKEN_INVALID else {
return nil
}
return token
}

/**
Atomically transition the JWT token to `OS_JWT_TOKEN_INVALID`. Returns
`true` if the transition occurred, `false` if the token was already invalid.
*/
@discardableResult
func invalidateJwtBearerToken() -> Bool {
let changed = lock.withLock {
guard jwtBearerTokenLocked != OS_JWT_TOKEN_INVALID else { return false }
jwtBearerTokenLocked = OS_JWT_TOKEN_INVALID
return true
}
if changed {
self.set(property: OS_JWT_BEARER_TOKEN, newValue: OS_JWT_TOKEN_INVALID)
}
return changed
}

// MARK: - Initialization
Expand All @@ -66,10 +98,10 @@ class OSIdentityModel: OSModel {
}

override func encode(with coder: NSCoder) {
aliasesLock.withLock {
lock.withLock {
super.encode(with: coder)
coder.encode(aliases, forKey: "aliases")
coder.encode(jwtBearerToken, forKey: OS_JWT_BEARER_TOKEN)
coder.encode(jwtBearerTokenLocked, forKey: OS_JWT_BEARER_TOKEN)
}
}

Expand All @@ -79,20 +111,20 @@ class OSIdentityModel: OSModel {
// log error
return nil
}
self.jwtBearerToken = coder.decodeObject(forKey: OS_JWT_BEARER_TOKEN) as? String
self.jwtBearerTokenLocked = coder.decodeObject(forKey: OS_JWT_BEARER_TOKEN) as? String
self.aliases = aliases
}

/** Threadsafe getter for an alias */
private func internalGetAlias(_ label: String) -> String? {
aliasesLock.withLock {
lock.withLock {
return self.aliases[label]
}
}

/** Threadsafe setter or removal for aliases */
private func internalAddAliases(_ aliases: [String: String]) {
aliasesLock.withLock {
lock.withLock {
for (label, id) in aliases {
// Remove the alias if the ID field is ""
self.aliases[label] = id.isEmpty ? nil : id
Expand All @@ -105,7 +137,7 @@ class OSIdentityModel: OSModel {
Called to clear the model's data in preparation for hydration via a fetch user call.
*/
func clearData() {
aliasesLock.withLock {
lock.withLock {
self.aliases = [:]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,19 @@ class OSIdentityModelRepo {
This can be optimized in the future to re-use an Identity Model if multiple logins are made for the same user.
*/
func updateJwtToken(externalId: String, token: String) {
var found = false
lock.withLock {
for model in models.values {
if model.externalId == externalId {
model.jwtBearerToken = token
found = true
}
}
// Snapshot matching models under the repo lock, then mutate outside.
// Writing the token fires the model's change notifier synchronously
// (→ onModelUpdated → onJwtTokenChanged); doing that while holding the
// repo lock leaves a trap for future listeners to deadlock on.
let matchingModels: [OSIdentityModel] = lock.withLock {
models.values.filter { $0.externalId == externalId }
}
if !found {
guard !matchingModels.isEmpty else {
OneSignalLog.onesignalLog(ONE_S_LOG_LEVEL.LL_ERROR, message: "Update User JWT called for external ID \(externalId) that does not exist")
return
}
for model in matchingModels {
model.jwtBearerToken = token
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {

// JWT is required

if _user.identityModel.isJwtValid(),
let token = _user.identityModel.jwtBearerToken
{
if let token = _user.identityModel.getValidJwt() {
fullHeader["Authorization"] = "Bearer \(token)"
return fullHeader
}
Expand Down Expand Up @@ -716,14 +714,9 @@ extension OneSignalUserManagerImpl {
return
}

// Return, if the token has already been invalidated
guard identityModel.jwtBearerToken != OS_JWT_TOKEN_INVALID else {
return
if identityModel.invalidateJwtBearerToken() {
fireJwtExpired(externalId: externalId)
}

identityModel.jwtBearerToken = OS_JWT_TOKEN_INVALID

fireJwtExpired(externalId: externalId)
}

private func fireJwtExpired(externalId: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,10 @@ internal extension OneSignalRequest {
| --------------- | -------------- | ------- | ------- |
*/
func addJWTHeaderIsValid(identityModel: OSIdentityModel) -> Bool {
let tokenIsValid = identityModel.isJwtValid()
let validToken = identityModel.getValidJwt()
let required = OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired
let canBeSent = (required == false) || (required == true && tokenIsValid)
if canBeSent && tokenIsValid,
let token = identityModel.jwtBearerToken
{
let canBeSent = (required == false) || (required == true && validToken != nil)
if canBeSent, let token = validToken {
// Add the JWT token if it is valid, regardless of requirements
var additionalHeaders = self.additionalHeaders ?? [String: String]()
additionalHeaders["Authorization"] = "Bearer \(token)"
Expand Down
Loading