233 lines
7.0 KiB
Java
233 lines
7.0 KiB
Java
// Copyright (C) 2009 The Android Open Source Project
|
|
//
|
|
// 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.
|
|
|
|
package com.google.gerrit.httpd;
|
|
|
|
import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64;
|
|
import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
|
|
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
|
|
import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
|
|
import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt64;
|
|
import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
|
|
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
|
|
import static java.util.concurrent.TimeUnit.HOURS;
|
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
|
|
import com.google.gerrit.reviewdb.Account;
|
|
import com.google.gerrit.reviewdb.AccountExternalId;
|
|
import com.google.gerrit.server.cache.Cache;
|
|
import com.google.inject.Inject;
|
|
import com.google.inject.Singleton;
|
|
import com.google.inject.name.Named;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
import java.io.Serializable;
|
|
import java.security.SecureRandom;
|
|
|
|
@Singleton
|
|
class WebSessionManager {
|
|
static final String CACHE_NAME = "web_sessions";
|
|
|
|
static long now() {
|
|
return System.currentTimeMillis();
|
|
}
|
|
|
|
private final SecureRandom prng;
|
|
private final Cache<Key, Val> self;
|
|
|
|
@Inject
|
|
WebSessionManager(@Named(CACHE_NAME) final Cache<Key, Val> cache) {
|
|
prng = new SecureRandom();
|
|
self = cache;
|
|
}
|
|
|
|
Key createKey(final Account.Id who) {
|
|
try {
|
|
final int nonceLen = 20;
|
|
final ByteArrayOutputStream buf;
|
|
final byte[] rnd = new byte[nonceLen];
|
|
prng.nextBytes(rnd);
|
|
|
|
buf = new ByteArrayOutputStream(3 + nonceLen);
|
|
writeVarInt32(buf, (int) Key.serialVersionUID);
|
|
writeVarInt32(buf, who.get());
|
|
writeBytes(buf, rnd);
|
|
|
|
return new Key(CookieBase64.encode(buf.toByteArray()));
|
|
} catch (IOException e) {
|
|
throw new RuntimeException("Cannot produce new account cookie", e);
|
|
}
|
|
}
|
|
|
|
Val createVal(final Key key, final Val val) {
|
|
final Account.Id who = val.getAccountId();
|
|
final boolean remember = val.isPersistentCookie();
|
|
final AccountExternalId.Key lastLogin = val.getExternalId();
|
|
|
|
return createVal(key, who, remember, lastLogin);
|
|
}
|
|
|
|
Val createVal(final Key key, final Account.Id who, final boolean remember,
|
|
final AccountExternalId.Key lastLogin) {
|
|
// Refresh the cookie every hour or when it is half-expired.
|
|
// This reduces the odds that the user session will be kicked
|
|
// early but also avoids us needing to refresh the cookie on
|
|
// every single request.
|
|
//
|
|
final long halfAgeRefresh = self.getTimeToLive(MILLISECONDS) >>> 1;
|
|
final long minRefresh = MILLISECONDS.convert(1, HOURS);
|
|
final long refresh = Math.min(halfAgeRefresh, minRefresh);
|
|
final long refreshCookieAt = now() + refresh;
|
|
|
|
final Val val = new Val(who, refreshCookieAt, remember, lastLogin);
|
|
self.put(key, val);
|
|
return val;
|
|
}
|
|
|
|
int getCookieAge(final Val val) {
|
|
if (val.isPersistentCookie()) {
|
|
// Client may store the cookie until we would remove it from our
|
|
// own cache, after which it will certainly be invalid.
|
|
//
|
|
return (int) self.getTimeToLive(SECONDS);
|
|
} else {
|
|
// Client should not store the cookie, as the user asked for us
|
|
// to not remember them long-term. Sending -1 as the age will
|
|
// cause the cookie to be only for this "browser session", which
|
|
// is usually until the user exits their browser.
|
|
//
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
Val get(final Key key) {
|
|
return self.get(key);
|
|
}
|
|
|
|
void destroy(final Key key) {
|
|
self.remove(key);
|
|
}
|
|
|
|
static final class Key implements Serializable {
|
|
static final long serialVersionUID = 2L;
|
|
|
|
private transient String token;
|
|
|
|
Key(final String t) {
|
|
token = t;
|
|
}
|
|
|
|
String getToken() {
|
|
return token;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return token.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
return obj instanceof Key && token.equals(((Key) obj).token);
|
|
}
|
|
|
|
private void writeObject(final ObjectOutputStream out) throws IOException {
|
|
writeString(out, token);
|
|
}
|
|
|
|
private void readObject(final ObjectInputStream in) throws IOException {
|
|
token = readString(in);
|
|
}
|
|
}
|
|
|
|
static final class Val implements Serializable {
|
|
static final long serialVersionUID = Key.serialVersionUID;
|
|
|
|
private transient Account.Id accountId;
|
|
private transient long refreshCookieAt;
|
|
private transient boolean persistentCookie;
|
|
private transient AccountExternalId.Key externalId;
|
|
|
|
Val(final Account.Id accountId, final long refreshCookieAt,
|
|
final boolean persistentCookie, final AccountExternalId.Key externalId) {
|
|
this.accountId = accountId;
|
|
this.refreshCookieAt = refreshCookieAt;
|
|
this.persistentCookie = persistentCookie;
|
|
this.externalId = externalId;
|
|
}
|
|
|
|
Account.Id getAccountId() {
|
|
return accountId;
|
|
}
|
|
|
|
AccountExternalId.Key getExternalId() {
|
|
return externalId;
|
|
}
|
|
|
|
boolean needsCookieRefresh() {
|
|
return refreshCookieAt <= now();
|
|
}
|
|
|
|
boolean isPersistentCookie() {
|
|
return persistentCookie;
|
|
}
|
|
|
|
private void writeObject(final ObjectOutputStream out) throws IOException {
|
|
writeVarInt32(out, 1);
|
|
writeVarInt32(out, accountId.get());
|
|
|
|
writeVarInt32(out, 2);
|
|
writeFixInt64(out, refreshCookieAt);
|
|
|
|
writeVarInt32(out, 3);
|
|
writeVarInt32(out, persistentCookie ? 1 : 0);
|
|
|
|
if (externalId != null) {
|
|
writeVarInt32(out, 4);
|
|
writeString(out, externalId.get());
|
|
}
|
|
|
|
writeVarInt32(out, 0);
|
|
}
|
|
|
|
private void readObject(final ObjectInputStream in) throws IOException {
|
|
PARSE: for (;;) {
|
|
final int tag = readVarInt32(in);
|
|
switch (tag) {
|
|
case 0:
|
|
break PARSE;
|
|
case 1:
|
|
accountId = new Account.Id(readVarInt32(in));
|
|
continue;
|
|
case 2:
|
|
refreshCookieAt = readFixInt64(in);
|
|
continue;
|
|
case 3:
|
|
persistentCookie = readVarInt32(in) != 0;
|
|
continue;
|
|
case 4:
|
|
externalId = new AccountExternalId.Key(readString(in));
|
|
continue;
|
|
default:
|
|
throw new IOException("Unknown tag found in object: " + tag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|