Skip to content

Commit 7a2d8df

Browse files
committed
Move OAuth integration to third_party.
It should have been in third_party to begin with. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=73826831
1 parent 603959e commit 7a2d8df

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.google.net.stubby.auth;
2+
3+
import com.google.api.client.auth.oauth2.Credential;
4+
import com.google.common.base.Preconditions;
5+
6+
import java.io.IOException;
7+
import java.util.concurrent.Executor;
8+
9+
import javax.inject.Provider;
10+
11+
/**
12+
* A {@link Provider} that produces valid OAuth2 access tokens given a {@link Credential}.
13+
*/
14+
class OAuth2AccessTokenProvider implements Provider<String> {
15+
private static final int LOWER_BOUND_ON_EXPIRATION_SECS = 5;
16+
private static final int LOWER_BOUND_ON_ASYNC_REFRESH_SECS = 60;
17+
18+
/**
19+
* Note that any call to Credential may block for long periods of time, due to shared lock with
20+
* refreshToken().
21+
*/
22+
private final Credential credential;
23+
private final Executor executor;
24+
private String accessToken;
25+
private Long expiresInSeconds;
26+
private boolean refreshing;
27+
28+
public OAuth2AccessTokenProvider(Credential credential, Executor executor) {
29+
this.credential = Preconditions.checkNotNull(credential);
30+
this.executor = Preconditions.checkNotNull(executor);
31+
// Access expiration before token to gracefully deals with races.
32+
expiresInSeconds = credential.getExpiresInSeconds();
33+
accessToken = credential.getAccessToken();
34+
}
35+
36+
private synchronized boolean isAccessTokenEffectivelyExpired() {
37+
return accessToken == null || expiresInSeconds == null
38+
|| expiresInSeconds <= LOWER_BOUND_ON_EXPIRATION_SECS;
39+
}
40+
41+
private synchronized boolean isAccessTokenSoonToBeExpired() {
42+
return accessToken == null || expiresInSeconds == null
43+
|| expiresInSeconds <= LOWER_BOUND_ON_ASYNC_REFRESH_SECS;
44+
}
45+
46+
@Override
47+
public synchronized String get() {
48+
if (isAccessTokenEffectivelyExpired()) {
49+
// Token is at best going to expire momentarily, so we must refresh before proceeding.
50+
makeSureTokenIsRefreshing();
51+
waitForTokenRefresh();
52+
if (isAccessTokenEffectivelyExpired()) {
53+
throw new RuntimeException("Could not obtain access token");
54+
}
55+
} else if (isAccessTokenSoonToBeExpired()) {
56+
makeSureTokenIsRefreshing();
57+
}
58+
return accessToken;
59+
}
60+
61+
private synchronized void makeSureTokenIsRefreshing() {
62+
if (refreshing) {
63+
return;
64+
}
65+
refreshing = true;
66+
executor.execute(new Runnable() {
67+
@Override
68+
public void run() {
69+
// Check if some other user of credential has already refreshed the access token.
70+
synchronized (OAuth2AccessTokenProvider.this) {
71+
expiresInSeconds = credential.getExpiresInSeconds();
72+
accessToken = credential.getAccessToken();
73+
if (!isAccessTokenSoonToBeExpired()) {
74+
refreshing = false;
75+
OAuth2AccessTokenProvider.this.notifyAll();
76+
return;
77+
}
78+
}
79+
try {
80+
credential.refreshToken();
81+
} catch (IOException ioe) {
82+
throw new RuntimeException(ioe);
83+
} finally {
84+
synchronized (OAuth2AccessTokenProvider.this) {
85+
refreshing = false;
86+
expiresInSeconds = credential.getExpiresInSeconds();
87+
accessToken = credential.getAccessToken();
88+
OAuth2AccessTokenProvider.this.notifyAll();
89+
}
90+
}
91+
}
92+
});
93+
}
94+
95+
private synchronized void waitForTokenRefresh() {
96+
while (refreshing) {
97+
try {
98+
wait();
99+
} catch (InterruptedException ex) {
100+
Thread.currentThread().interrupt();
101+
throw new RuntimeException(ex);
102+
}
103+
}
104+
}
105+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.google.net.stubby.auth;
2+
3+
import com.google.api.client.auth.oauth2.Credential;
4+
import com.google.common.base.Preconditions;
5+
import com.google.net.stubby.Call;
6+
import com.google.net.stubby.Channel;
7+
import com.google.net.stubby.MethodDescriptor;
8+
9+
import java.util.concurrent.Executor;
10+
11+
import javax.inject.Provider;
12+
13+
/** Channel wrapper that authenticates all calls with OAuth2. */
14+
public class OAuth2ChannelInterceptor implements Channel {
15+
private final Channel delegate;
16+
private final OAuth2AccessTokenProvider accessTokenProvider;
17+
private final Provider<String> authorizationHeaderProvider
18+
= new Provider<String>() {
19+
@Override
20+
public String get() {
21+
return "Bearer " + accessTokenProvider.get();
22+
}
23+
};
24+
25+
public OAuth2ChannelInterceptor(Channel delegate, Credential credential, Executor executor) {
26+
this.delegate = Preconditions.checkNotNull(delegate);
27+
this.accessTokenProvider = new OAuth2AccessTokenProvider(credential, executor);
28+
}
29+
30+
@Override
31+
public <ReqT, RespT> Call<ReqT, RespT> newCall(MethodDescriptor<ReqT, RespT> method) {
32+
// TODO(user): If the call fails for Auth reasons, this does not properly propagate info that
33+
// would be in WWW-Authenticate, because it does not yet have access to the header.
34+
return delegate.newCall(method.withHeader("Authorization", authorizationHeaderProvider));
35+
}
36+
}

0 commit comments

Comments
 (0)