Skip to content

Commit b86198d

Browse files
authored
Merge pull request #1347 from couchbase/feature/issue_1338
Fixed #1338 - OpenID tokens in the key store should be per-database
2 parents 97de7d1 + d096f8c commit b86198d

9 files changed

Lines changed: 77 additions & 47 deletions

File tree

src/main/java/com/couchbase/lite/Manager.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,22 @@ public Replication getReplicator(Map<String, Object> properties) throws Couchbas
628628
remoteMap = sourceMap;
629629
}
630630

631+
// Can't specify both a filter and doc IDs
632+
if (properties.get("filter") != null && properties.get("doc_ids") != null)
633+
throw new CouchbaseLiteException("Can't specify both a filter and doc IDs",
634+
new Status(Status.BAD_REQUEST));
635+
636+
try {
637+
remote = new URL(remoteStr);
638+
} catch (MalformedURLException e) {
639+
throw new CouchbaseLiteException("malformed remote url: " + remoteStr,
640+
new Status(Status.BAD_REQUEST));
641+
}
642+
if (remote == null) {
643+
throw new CouchbaseLiteException("remote URL is null: " + remoteStr,
644+
new Status(Status.BAD_REQUEST));
645+
}
646+
631647
Map<String, Object> authMap = (Map<String, Object>) remoteMap.get("auth");
632648
if (authMap != null) {
633649

@@ -641,23 +657,11 @@ public Replication getReplicator(Map<String, Object> properties) throws Couchbas
641657
String email = (String) facebook.get("email");
642658
authorizer = new FacebookAuthorizer(email);
643659
}
660+
authorizer.setRemoteURL(remote);
661+
authorizer.setLocalUUID(db.publicUUID());
644662
}
645663

646-
// Can't specify both a filter and doc IDs
647-
if (properties.get("filter") != null && properties.get("doc_ids") != null)
648-
throw new CouchbaseLiteException("Can't specify both a filter and doc IDs",
649-
new Status(Status.BAD_REQUEST));
650664

651-
try {
652-
remote = new URL(remoteStr);
653-
} catch (MalformedURLException e) {
654-
throw new CouchbaseLiteException("malformed remote url: " + remoteStr,
655-
new Status(Status.BAD_REQUEST));
656-
}
657-
if (remote == null) {
658-
throw new CouchbaseLiteException("remote URL is null: " + remoteStr,
659-
new Status(Status.BAD_REQUEST));
660-
}
661665

662666
if (!cancel) {
663667
repl = db.getReplicator(remote, getDefaultHttpClientFactory(), push, continuous);

src/main/java/com/couchbase/lite/auth/Authorizer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ public interface Authorizer extends Authenticator {
2727

2828
void setRemoteURL(URL remoteURL);
2929

30+
/**
31+
* The unique ID of the local database. The replicator sets this property when it starts up.
32+
*/
33+
String getLocalUUID();
34+
35+
void setLocalUUID(String localUUID);
36+
3037
boolean removeStoredCredentials();
3138

3239
// @optional

src/main/java/com/couchbase/lite/auth/BaseAuthorizer.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public abstract class BaseAuthorizer implements Authorizer {
2222
////////////////////////////////////////////////////////////
2323
// Member variables
2424
////////////////////////////////////////////////////////////
25-
protected URL remoteURL;
25+
private URL remoteURL;
26+
private String localUUID;
2627

2728
////////////////////////////////////////////////////////////
2829
// Implementations of Authorizer
@@ -43,6 +44,16 @@ public boolean removeStoredCredentials() {
4344
return true;
4445
}
4546

47+
@Override
48+
public String getLocalUUID() {
49+
return localUUID;
50+
}
51+
52+
@Override
53+
public void setLocalUUID(String localUUID) {
54+
this.localUUID = localUUID;
55+
}
56+
4657
@Override
4758
public String getUsername() {
4859
// @optional

src/main/java/com/couchbase/lite/auth/FacebookAuthorizer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static boolean registerToken(String token, String email, URL site) {
101101
private String token() {
102102
List<String> key = new ArrayList<String>();
103103
key.add(email);
104-
key.add(remoteURL.toExternalForm().toLowerCase());
104+
key.add(getRemoteURL().toExternalForm().toLowerCase());
105105
Log.v(TAG, "FacebookAuthorizer looking up key: %s from list of access tokens", key);
106106
return sRegisteredTokens.get(key);
107107
}

src/main/java/com/couchbase/lite/auth/MemTokenStore.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,47 @@ public class MemTokenStore implements TokenStore {
2525
private Map<String, Map<String, String>> store = new HashMap<String, Map<String, String>>();
2626

2727
@Override
28-
public Map<String, String> loadTokens(URL remoteURL) throws Exception {
29-
if(remoteURL == null)
28+
public Map<String, String> loadTokens(URL remoteURL, String localUUID) throws Exception {
29+
if (remoteURL == null)
3030
return null;
31-
String key = getKey(remoteURL);
31+
String key = getKey(remoteURL, localUUID);
3232
if (!store.containsKey(key))
3333
return null;
3434
return store.get(key);
3535
}
3636

3737
@Override
38-
public boolean saveTokens(URL remoteURL, Map<String, String> tokens) {
39-
if(tokens == null)
40-
return deleteTokens(remoteURL);
38+
public boolean saveTokens(URL remoteURL, String localUUID, Map<String, String> tokens) {
39+
if (tokens == null)
40+
return deleteTokens(remoteURL, localUUID);
4141

42-
if(remoteURL == null)
42+
if (remoteURL == null)
4343
return false;
4444

45-
String key = getKey(remoteURL);
45+
String key = getKey(remoteURL, localUUID);
4646
store.put(key, tokens);
4747
return true;
4848
}
4949

5050
@Override
51-
public boolean deleteTokens(URL remoteURL) {
52-
if(remoteURL == null)
51+
public boolean deleteTokens(URL remoteURL, String localUUID) {
52+
if (remoteURL == null)
5353
return false;
54-
String key = getKey(remoteURL);
54+
String key = getKey(remoteURL, localUUID);
5555
if (!store.containsKey(key))
5656
return false;
5757
store.remove(key);
5858
return true;
5959
}
6060

61-
/*package*/ String getKey(URL remoteURL) {
62-
if(remoteURL == null)
61+
/*package*/ String getKey(URL remoteURL, String localUUID) {
62+
if (remoteURL == null)
6363
throw new IllegalArgumentException("remoteURL is null");
64-
String account = remoteURL.toExternalForm();
64+
String service = remoteURL.toExternalForm();
6565
String label = String.format(Locale.ENGLISH, "%s OpenID Connect tokens", remoteURL.getHost());
66-
return String.format(Locale.ENGLISH, "%s%s", label, account);
66+
if (localUUID == null)
67+
return String.format(Locale.ENGLISH, "%s%s", label, service);
68+
else
69+
return String.format(Locale.ENGLISH, "%s%s%s", label, service, localUUID);
6770
}
6871
}

src/main/java/com/couchbase/lite/auth/OpenIDConnectAuthorizer.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public OpenIDConnectAuthorizer(OIDCLoginCallback callback, TokenStore tokenStore
6666
////////////////////////////////////////////////////////////
6767
@Override
6868
public String toString() {
69-
return String.format(Locale.ENGLISH, "OpenIDConnectAuthorizer[%s]", remoteURL);
69+
return String.format(Locale.ENGLISH, "OpenIDConnectAuthorizer[%s]", getRemoteURL());
7070
}
7171

7272
////////////////////////////////////////////////////////////
@@ -120,7 +120,7 @@ public void loginResponse(Object jsonResponse,
120120
Throwable error,
121121
ContinuationBlock block) {
122122
if (error != null && (!(error instanceof RemoteRequestResponseException) ||
123-
((RemoteRequestResponseException) error).getCode() != 401)) {
123+
((RemoteRequestResponseException) error).getCode() != 401)) {
124124
block.call(false, error);
125125
return;
126126
}
@@ -195,7 +195,7 @@ public boolean implementedLoginResponse() {
195195

196196
@Override
197197
public boolean removeStoredCredentials() {
198-
if(!deleteTokens())
198+
if (!deleteTokens())
199199
return false;
200200
IDToken = null;
201201
refreshToken = null;
@@ -248,6 +248,8 @@ public void setRefreshToken(String refreshToken) {
248248
public static boolean forgetIDTokensForServer(URL serverURL, TokenStore tokenStore) {
249249
OpenIDConnectAuthorizer authorizer = new OpenIDConnectAuthorizer(null, tokenStore);
250250
authorizer.setRemoteURL(serverURL);
251+
// Deliberately don't set auth.localUUID. This will leave kSecAttrAccount unset in the
252+
// dictionary passed to SecItemDelete, deleting keychain items for all accounts (databases).
251253
return authorizer.deleteTokens();
252254
}
253255

@@ -260,7 +262,7 @@ public static boolean forgetIDTokensForServer(URL serverURL, TokenStore tokenSto
260262
return false;
261263

262264
try {
263-
return parseTokens(tokenStore.loadTokens(remoteURL));
265+
return parseTokens(tokenStore.loadTokens(getRemoteURL(), getLocalUUID()));
264266
} catch (Exception e) {
265267
Log.w(TAG, "Error in loadTokens()", e);
266268
return false;
@@ -270,13 +272,13 @@ public static boolean forgetIDTokensForServer(URL serverURL, TokenStore tokenSto
270272
/*package*/ boolean saveTokens(Map<String, String> tokens) {
271273
if (tokenStore == null)
272274
return false;
273-
return tokenStore.saveTokens(remoteURL, tokens);
275+
return tokenStore.saveTokens(getRemoteURL(), getLocalUUID(), tokens);
274276
}
275277

276278
/*package*/ boolean deleteTokens() {
277279
if (tokenStore == null)
278280
return false;
279-
return tokenStore.deleteTokens(remoteURL);
281+
return tokenStore.deleteTokens(getRemoteURL(), getLocalUUID());
280282
}
281283

282284
private boolean parseTokens(Map<String, String> tokens) {
@@ -301,7 +303,7 @@ private boolean parseTokens(Map<String, String> tokens) {
301303

302304
private void continueAsyncLoginWithURL(URL loginURL, final ContinuationBlock block) {
303305
Log.v(TAG, "OpenIDConnectAuthorizer: Calling app login callback block...");
304-
final URL remoteURL = this.remoteURL;
306+
final URL remoteURL = this.getRemoteURL();
305307
final URL redirectBaseURL = extractRedirectURL(loginURL);
306308
if (loginCallback != null)
307309
loginCallback.callback(loginURL, redirectBaseURL, new OIDCLoginContinuation() {
@@ -312,8 +314,8 @@ public void callback(URL url, Throwable error) {
312314
"<%s>", url.toExternalForm());
313315
// Verify that the authURL matches the site:
314316
if (remoteURL == null ||
315-
url.getHost().compareToIgnoreCase(remoteURL.getHost()) != 0 ||
316-
url.getPort() != remoteURL.getPort()) {
317+
url.getHost().compareToIgnoreCase(remoteURL.getHost()) != 0 ||
318+
url.getPort() != remoteURL.getPort()) {
317319
Log.w(TAG, "OpenIDConnectAuthorizer: App-provided authURL <%s> " +
318320
"doesn't match server URL; ignoring it", url.toExternalForm());
319321
url = null;

src/main/java/com/couchbase/lite/auth/PersonaAuthorizer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ public static String assertionForEmailAndSite(String email, URL site) {
147147
////////////////////////////////////////////////////////////
148148

149149
/*package*/ String assertion() {
150-
String assertion = assertionForEmailAndSite(email, remoteURL);
150+
String assertion = assertionForEmailAndSite(email, getRemoteURL());
151151
if (assertion == null) {
152-
Log.w(TAG, "PersonaAuthorizer<%s> no assertion found for: %s", email, remoteURL);
152+
Log.w(TAG, "PersonaAuthorizer<%s> no assertion found for: %s", email, getRemoteURL());
153153
return null;
154154
}
155155
Map<String, Object> result = parseAssertion(assertion);

src/main/java/com/couchbase/lite/auth/TokenStore.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,20 @@ public interface TokenStore {
2323
*
2424
* @return tokens
2525
*/
26-
Map<String, String> loadTokens(URL remoteURL) throws Exception;
26+
Map<String, String> loadTokens(URL remoteURL, String localUUID) throws Exception;
2727

2828
/**
2929
* Save tokens into the token store
3030
*
3131
* @param tokens to be saved
3232
* @return true - success, false - failure
3333
*/
34-
boolean saveTokens(URL remoteURL, Map<String, String> tokens);
34+
boolean saveTokens(URL remoteURL, String localUUID, Map<String, String> tokens);
3535

3636
/**
3737
* Delete tokens from the token store
3838
*
3939
* @return true - success, false - failure
4040
*/
41-
boolean deleteTokens(URL remoteURL);
41+
boolean deleteTokens(URL remoteURL, String localUUID);
4242
}

src/main/java/com/couchbase/lite/replicator/ReplicationInternal.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,9 @@ protected void close() {
335335

336336
protected void initAuthorizer() {
337337
if (authenticator != null && authenticator instanceof Authorizer) {
338-
((Authorizer) authenticator).setRemoteURL(remote);
338+
Authorizer authorizer = (Authorizer)authenticator;
339+
authorizer.setRemoteURL(remote);
340+
authorizer.setLocalUUID(db.publicUUID());
339341
}
340342
}
341343

@@ -430,7 +432,8 @@ public Thread newThread(Runnable r) {
430432
protected void checkSession() {
431433
if (getAuthenticator() != null) {
432434
Authorizer auth = (Authorizer) getAuthenticator();
433-
auth.setRemoteURL(this.remote);
435+
auth.setRemoteURL(remote);
436+
auth.setLocalUUID(db.publicUUID());
434437
}
435438
if (getAuthenticator() != null && getAuthenticator() instanceof SessionCookieAuthorizer) {
436439
// Sync Gateway session API is at /db/_session; try that first

0 commit comments

Comments
 (0)