Registration API. Add DAO, Manager.
This commit is contained in:
78
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
78
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package io.kamax.mxisd.auth;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.exception.NotFoundException;
|
||||
import io.kamax.mxisd.matrix.HomeserverFederationResolver;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AccountManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountManager.class);
|
||||
|
||||
private final IStorage storage;
|
||||
private final HomeserverFederationResolver resolver;
|
||||
private final CloseableHttpClient httpClient;
|
||||
|
||||
public AccountManager(IStorage storage, HomeserverFederationResolver resolver,
|
||||
CloseableHttpClient httpClient) {
|
||||
this.storage = storage;
|
||||
this.resolver = resolver;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public String register(OpenIdToken openIdToken) {
|
||||
Objects.requireNonNull(openIdToken.getAccessToken(), "Missing required access_token");
|
||||
Objects.requireNonNull(openIdToken.getTokenType(), "Missing required token type");
|
||||
Objects.requireNonNull(openIdToken.getMatrixServerName(), "Missing required matrix domain");
|
||||
|
||||
String homeserverURL = resolver.resolve(openIdToken.getMatrixServerName()).toString();
|
||||
HttpGet getUserInfo = new HttpGet(
|
||||
"https://" + homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken());
|
||||
String userId;
|
||||
try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
JsonObject body = GsonUtil.parseObj(EntityUtils.toString(response.getEntity()));
|
||||
userId = GsonUtil.getStringOrThrow(body, "sub");
|
||||
} else {
|
||||
LOGGER.error("Wrong response status: {}", statusCode);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Unable to get user info.");
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
String token = UUID.randomUUID().toString();
|
||||
AccountDao account = new AccountDao(openIdToken.getAccessToken(), openIdToken.getTokenType(),
|
||||
openIdToken.getMatrixServerName(), openIdToken.getExpiredIn(),
|
||||
Instant.now().getEpochSecond(), userId, token);
|
||||
storage.insertToken(account);
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getUserId(String token) {
|
||||
return storage.findUserId(token).orElseThrow(NotFoundException::new);
|
||||
}
|
||||
|
||||
public void logout(String token) {
|
||||
String userId = storage.findUserId(token).orElseThrow(InvalidCredentialsException::new);
|
||||
LOGGER.info("Logout: {}", userId);
|
||||
storage.deleteToken(token);
|
||||
}
|
||||
}
|
44
src/main/java/io/kamax/mxisd/auth/OpenIdToken.java
Normal file
44
src/main/java/io/kamax/mxisd/auth/OpenIdToken.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package io.kamax.mxisd.auth;
|
||||
|
||||
public class OpenIdToken {
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private String tokenType;
|
||||
|
||||
private String matrixServerName;
|
||||
|
||||
private long expiredIn;
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public String getMatrixServerName() {
|
||||
return matrixServerName;
|
||||
}
|
||||
|
||||
public void setMatrixServerName(String matrixServerName) {
|
||||
this.matrixServerName = matrixServerName;
|
||||
}
|
||||
|
||||
public long getExpiredIn() {
|
||||
return expiredIn;
|
||||
}
|
||||
|
||||
public void setExpiredIn(long expiredIn) {
|
||||
this.expiredIn = expiredIn;
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2018 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.auth.v2;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.auth.AuthManager;
|
||||
import io.kamax.mxisd.auth.UserAuthResult;
|
||||
import io.kamax.mxisd.exception.JsonMemberNotFoundException;
|
||||
import io.kamax.mxisd.http.io.CredentialsValidationResponse;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AccountRegisterHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = "/_matrix/identity/v2/account/register";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AccountRegisterHandler.class);
|
||||
|
||||
private AuthManager mgr;
|
||||
|
||||
public AccountRegisterHandler(AuthManager mgr) {
|
||||
this.mgr = mgr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
JsonObject authData = parseJsonObject(exchange, "user");
|
||||
if (!authData.has("id") || !authData.has("password")) {
|
||||
throw new JsonMemberNotFoundException("Missing id or password keys");
|
||||
}
|
||||
|
||||
String id = GsonUtil.getStringOrThrow(authData, "id");
|
||||
log.info("Requested to check credentials for {}", id);
|
||||
String password = GsonUtil.getStringOrThrow(authData, "password");
|
||||
|
||||
UserAuthResult result = mgr.authenticate(id, password);
|
||||
CredentialsValidationResponse response = new CredentialsValidationResponse(result.isSuccess());
|
||||
|
||||
if (result.isSuccess()) {
|
||||
response.setDisplayName(result.getDisplayName());
|
||||
response.getProfile().setThreePids(result.getThreePids());
|
||||
}
|
||||
|
||||
JsonElement authObj = GsonUtil.get().toJsonTree(response);
|
||||
respond(exchange, GsonUtil.makeObj("auth", authObj));
|
||||
}
|
||||
|
||||
}
|
@@ -24,6 +24,7 @@ import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -52,4 +53,9 @@ public interface IStorage {
|
||||
|
||||
Optional<ASTransactionDao> getTransactionResult(String localpart, String txnId);
|
||||
|
||||
void insertToken(AccountDao accountDao);
|
||||
|
||||
Optional<String> findUserId(String accessToken);
|
||||
|
||||
void deleteToken(String accessToken);
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.HistoricalThreePidInviteIO;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ThreePidSessionDao;
|
||||
@@ -42,7 +43,11 @@ import org.apache.commons.lang.StringUtils;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
@@ -64,6 +69,7 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
private Dao<HistoricalThreePidInviteIO, String> expInvDao;
|
||||
private Dao<ThreePidSessionDao, String> sessionDao;
|
||||
private Dao<ASTransactionDao, String> asTxnDao;
|
||||
private Dao<AccountDao, String> accountDao;
|
||||
|
||||
public OrmLiteSqlStorage(MxisdConfig cfg) {
|
||||
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
|
||||
@@ -84,6 +90,7 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class);
|
||||
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
||||
asTxnDao = createDaoAndTable(connPool, ASTransactionDao.class);
|
||||
accountDao = createDaoAndTable(connPool, AccountDao.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,7 +182,7 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
List<ThreePidSessionDao> daoList = sessionDao.queryForMatchingArgs(new ThreePidSessionDao(tpid, secret));
|
||||
if (daoList.size() > 1) {
|
||||
throw new InternalServerError("Lookup for 3PID Session " +
|
||||
tpid + " returned more than one result");
|
||||
tpid + " returned more than one result");
|
||||
}
|
||||
|
||||
if (daoList.isEmpty()) {
|
||||
@@ -226,7 +233,7 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
if (daoList.size() > 1) {
|
||||
throw new InternalServerError("Lookup for Transaction " +
|
||||
txnId + " for localpart " + localpart + " returned more than one result");
|
||||
txnId + " for localpart " + localpart + " returned more than one result");
|
||||
}
|
||||
|
||||
if (daoList.isEmpty()) {
|
||||
@@ -237,4 +244,37 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertToken(AccountDao account) {
|
||||
withCatcher(() -> {
|
||||
int created = accountDao.create(account);
|
||||
if (created != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + created);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> findUserId(String token) {
|
||||
return withCatcher(() -> {
|
||||
List<AccountDao> accounts = accountDao.queryForEq("token", token);
|
||||
if (accounts.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (accounts.size() != 1) {
|
||||
throw new RuntimeException("Unexpected rows for access token: " + accounts.size());
|
||||
}
|
||||
return Optional.of(accounts.get(0).getUserId());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteToken(String token) {
|
||||
withCatcher(() -> {
|
||||
int updated = accountDao.deleteById(token);
|
||||
if (updated != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
119
src/main/java/io/kamax/mxisd/storage/ormlite/dao/AccountDao.java
Normal file
119
src/main/java/io/kamax/mxisd/storage/ormlite/dao/AccountDao.java
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2018 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage.ormlite.dao;
|
||||
|
||||
import com.j256.ormlite.field.DatabaseField;
|
||||
import com.j256.ormlite.table.DatabaseTable;
|
||||
|
||||
@DatabaseTable(tableName = "account")
|
||||
public class AccountDao {
|
||||
|
||||
@DatabaseField(canBeNull = false, id = true)
|
||||
private String token;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String accessToken;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String tokenType;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String matrixServerName;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private long expiresIn;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private long createdAt;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String userId;
|
||||
|
||||
public AccountDao() {
|
||||
// Needed for ORMLite
|
||||
}
|
||||
|
||||
public AccountDao(String accessToken, String tokenType, String matrixServerName, long expiresIn, long createdAt, String userId, String token) {
|
||||
this.accessToken = accessToken;
|
||||
this.tokenType = tokenType;
|
||||
this.matrixServerName = matrixServerName;
|
||||
this.expiresIn = expiresIn;
|
||||
this.createdAt = createdAt;
|
||||
this.userId = userId;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public String getMatrixServerName() {
|
||||
return matrixServerName;
|
||||
}
|
||||
|
||||
public void setMatrixServerName(String matrixServerName) {
|
||||
this.matrixServerName = matrixServerName;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public long getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(long createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user