mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-19 21:50:45 +00:00
Add table-based authentication module; update javalin to support Authentication: Basic header; Move authentication classes
This commit is contained in:
@ -28,8 +28,8 @@ import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
@ -29,6 +29,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
public enum QAuthenticationType
|
||||
{
|
||||
AUTH_0("auth0"),
|
||||
TABLE_BASED("tableBased"),
|
||||
FULLY_ANONYMOUS("fullyAnonymous"),
|
||||
MOCK("mock");
|
||||
|
||||
|
@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
@ -48,7 +49,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import io.github.cdimascio.dotenv.DotenvEntry;
|
||||
|
@ -19,11 +19,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication.metadata;
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.authentication;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -49,6 +51,11 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
|
||||
{
|
||||
super();
|
||||
setType(QAuthenticationType.AUTH_0);
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// ensure this module is registered with the dispatcher //
|
||||
//////////////////////////////////////////////////////////
|
||||
QAuthenticationModuleDispatcher.registerModule(QAuthenticationType.AUTH_0.getName(), Auth0AuthenticationModule.class.getName());
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication.metadata;
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.authentication;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
@ -0,0 +1,454 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.authentication;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.TableBasedAuthenticationModule;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to provide details of an Auth0 Authentication module
|
||||
*******************************************************************************/
|
||||
public class TableBasedAuthenticationMetaData extends QAuthenticationMetaData
|
||||
{
|
||||
private String userTableName = "user";
|
||||
private String userTablePrimaryKeyField = "id";
|
||||
private String userTableUsernameField = "username";
|
||||
private String userTablePasswordHashField = "passwordHash";
|
||||
private String userTableFullNameField = "fullName";
|
||||
|
||||
private String sessionTableName = "session";
|
||||
private String sessionTablePrimaryKeyField = "id";
|
||||
private String sessionTableUuidField = "id";
|
||||
private String sessionTableUserIdField = "userId";
|
||||
private String sessionTableAccessTimestampField = "accessTimestamp";
|
||||
|
||||
private Integer inactivityTimeoutSeconds = 14_400; // 4 hours
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default Constructor.
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData()
|
||||
{
|
||||
super();
|
||||
setType(QAuthenticationType.TABLE_BASED);
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// ensure this module is registered with the dispatcher //
|
||||
//////////////////////////////////////////////////////////
|
||||
QAuthenticationModuleDispatcher.registerModule(QAuthenticationType.TABLE_BASED.getName(), TableBasedAuthenticationModule.class.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData defineStandardUserTable(String backendName)
|
||||
{
|
||||
return (new QTableMetaData()
|
||||
.withName(getUserTableName())
|
||||
.withBackendName(backendName)
|
||||
.withPrimaryKeyField(getUserTablePrimaryKeyField())
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields(getUserTableUsernameField())
|
||||
.withUniqueKey(new UniqueKey(getUserTableUsernameField()))
|
||||
.withField(new QFieldMetaData(getUserTablePrimaryKeyField(), QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME))
|
||||
.withField(new QFieldMetaData(getUserTablePrimaryKeyField(), QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData(getUserTableUsernameField(), QFieldType.STRING))
|
||||
.withField(new QFieldMetaData(getUserTablePasswordHashField(), QFieldType.STRING))
|
||||
.withField(new QFieldMetaData(getUserTableFullNameField(), QFieldType.STRING)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData defineStandardSessionTable(String backendName)
|
||||
{
|
||||
return (new QTableMetaData()
|
||||
.withName(getSessionTableName())
|
||||
.withBackendName(backendName)
|
||||
.withPrimaryKeyField(getSessionTablePrimaryKeyField())
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields(getSessionTableUuidField())
|
||||
.withUniqueKey(new UniqueKey(getSessionTableUuidField()))
|
||||
.withField(new QFieldMetaData(getSessionTableUuidField(), QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME))
|
||||
.withField(new QFieldMetaData(getSessionTableUserIdField(), QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData(getSessionTableAccessTimestampField(), QFieldType.DATE_TIME)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userTableName
|
||||
*******************************************************************************/
|
||||
public String getUserTableName()
|
||||
{
|
||||
return (this.userTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userTableName
|
||||
*******************************************************************************/
|
||||
public void setUserTableName(String userTableName)
|
||||
{
|
||||
this.userTableName = userTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userTableName
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withUserTableName(String userTableName)
|
||||
{
|
||||
this.userTableName = userTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sessionTableName
|
||||
*******************************************************************************/
|
||||
public String getSessionTableName()
|
||||
{
|
||||
return (this.sessionTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionTableName
|
||||
*******************************************************************************/
|
||||
public void setSessionTableName(String sessionTableName)
|
||||
{
|
||||
this.sessionTableName = sessionTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sessionTableName
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withSessionTableName(String sessionTableName)
|
||||
{
|
||||
this.sessionTableName = sessionTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userTablePrimaryKeyField
|
||||
*******************************************************************************/
|
||||
public String getUserTablePrimaryKeyField()
|
||||
{
|
||||
return (this.userTablePrimaryKeyField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userTablePrimaryKeyField
|
||||
*******************************************************************************/
|
||||
public void setUserTablePrimaryKeyField(String userTablePrimaryKeyField)
|
||||
{
|
||||
this.userTablePrimaryKeyField = userTablePrimaryKeyField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userTablePrimaryKeyField
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withUserTablePrimaryKeyField(String userTablePrimaryKeyField)
|
||||
{
|
||||
this.userTablePrimaryKeyField = userTablePrimaryKeyField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userTableUsernameField
|
||||
*******************************************************************************/
|
||||
public String getUserTableUsernameField()
|
||||
{
|
||||
return (this.userTableUsernameField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userTableUsernameField
|
||||
*******************************************************************************/
|
||||
public void setUserTableUsernameField(String userTableUsernameField)
|
||||
{
|
||||
this.userTableUsernameField = userTableUsernameField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userTableUsernameField
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withUserTableUsernameField(String userTableUsernameField)
|
||||
{
|
||||
this.userTableUsernameField = userTableUsernameField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sessionTablePrimaryKeyField
|
||||
*******************************************************************************/
|
||||
public String getSessionTablePrimaryKeyField()
|
||||
{
|
||||
return (this.sessionTablePrimaryKeyField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionTablePrimaryKeyField
|
||||
*******************************************************************************/
|
||||
public void setSessionTablePrimaryKeyField(String sessionTablePrimaryKeyField)
|
||||
{
|
||||
this.sessionTablePrimaryKeyField = sessionTablePrimaryKeyField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sessionTablePrimaryKeyField
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withSessionTablePrimaryKeyField(String sessionTablePrimaryKeyField)
|
||||
{
|
||||
this.sessionTablePrimaryKeyField = sessionTablePrimaryKeyField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sessionTableUserIdField
|
||||
*******************************************************************************/
|
||||
public String getSessionTableUserIdField()
|
||||
{
|
||||
return (this.sessionTableUserIdField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionTableUserIdField
|
||||
*******************************************************************************/
|
||||
public void setSessionTableUserIdField(String sessionTableUserIdField)
|
||||
{
|
||||
this.sessionTableUserIdField = sessionTableUserIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sessionTableUserIdField
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withSessionTableUserIdField(String sessionTableUserIdField)
|
||||
{
|
||||
this.sessionTableUserIdField = sessionTableUserIdField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sessionTableUuidField
|
||||
*******************************************************************************/
|
||||
public String getSessionTableUuidField()
|
||||
{
|
||||
return (this.sessionTableUuidField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionTableUuidField
|
||||
*******************************************************************************/
|
||||
public void setSessionTableUuidField(String sessionTableUuidField)
|
||||
{
|
||||
this.sessionTableUuidField = sessionTableUuidField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sessionTableUuidField
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withSessionTableUuidField(String sessionTableUuidField)
|
||||
{
|
||||
this.sessionTableUuidField = sessionTableUuidField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userTableFullNameField
|
||||
*******************************************************************************/
|
||||
public String getUserTableFullNameField()
|
||||
{
|
||||
return (this.userTableFullNameField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userTableFullNameField
|
||||
*******************************************************************************/
|
||||
public void setUserTableFullNameField(String userTableFullNameField)
|
||||
{
|
||||
this.userTableFullNameField = userTableFullNameField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userTableFullNameField
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withUserTableFullNameField(String userTableFullNameField)
|
||||
{
|
||||
this.userTableFullNameField = userTableFullNameField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userTablePasswordHashField
|
||||
*******************************************************************************/
|
||||
public String getUserTablePasswordHashField()
|
||||
{
|
||||
return (this.userTablePasswordHashField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userTablePasswordHashField
|
||||
*******************************************************************************/
|
||||
public void setUserTablePasswordHashField(String userTablePasswordHashField)
|
||||
{
|
||||
this.userTablePasswordHashField = userTablePasswordHashField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userTablePasswordHashField
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withUserTablePasswordHashField(String userTablePasswordHashField)
|
||||
{
|
||||
this.userTablePasswordHashField = userTablePasswordHashField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sessionTableAccessTimestampField
|
||||
*******************************************************************************/
|
||||
public String getSessionTableAccessTimestampField()
|
||||
{
|
||||
return (this.sessionTableAccessTimestampField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sessionTableAccessTimestampField
|
||||
*******************************************************************************/
|
||||
public void setSessionTableAccessTimestampField(String sessionTableAccessTimestampField)
|
||||
{
|
||||
this.sessionTableAccessTimestampField = sessionTableAccessTimestampField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sessionTableAccessTimestampField
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withSessionTableAccessTimestampField(String sessionTableAccessTimestampField)
|
||||
{
|
||||
this.sessionTableAccessTimestampField = sessionTableAccessTimestampField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for inactivityTimeoutSeconds
|
||||
*******************************************************************************/
|
||||
public Integer getInactivityTimeoutSeconds()
|
||||
{
|
||||
return (this.inactivityTimeoutSeconds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for inactivityTimeoutSeconds
|
||||
*******************************************************************************/
|
||||
public void setInactivityTimeoutSeconds(Integer inactivityTimeoutSeconds)
|
||||
{
|
||||
this.inactivityTimeoutSeconds = inactivityTimeoutSeconds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for inactivityTimeoutSeconds
|
||||
*******************************************************************************/
|
||||
public TableBasedAuthenticationMetaData withInactivityTimeoutSeconds(Integer inactivityTimeoutSeconds)
|
||||
{
|
||||
this.inactivityTimeoutSeconds = inactivityTimeoutSeconds;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -22,11 +22,14 @@
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.FullyAnonymousAuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.MockAuthenticationModule;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -38,20 +41,35 @@ import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthentic
|
||||
*******************************************************************************/
|
||||
public class QAuthenticationModuleDispatcher
|
||||
{
|
||||
private final Map<String, String> authenticationTypeToModuleClassNameMap;
|
||||
|
||||
private static Map<String, String> authenticationTypeToModuleClassNameMap = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
static
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ensure our 2 default modules are registered. //
|
||||
// Note that for "real" implementations, the pattern is for their MetaData class's constructor to register. //
|
||||
// the idea being, any qInstance using such a module, surely will have some MetaData defined for it. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
registerModule(QAuthenticationType.MOCK.getName(), MockAuthenticationModule.class.getName());
|
||||
registerModule(QAuthenticationType.FULLY_ANONYMOUS.getName(), FullyAnonymousAuthenticationModule.class.getName());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAuthenticationModuleDispatcher()
|
||||
{
|
||||
authenticationTypeToModuleClassNameMap = new HashMap<>();
|
||||
authenticationTypeToModuleClassNameMap.put(QAuthenticationType.MOCK.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule");
|
||||
authenticationTypeToModuleClassNameMap.put(QAuthenticationType.FULLY_ANONYMOUS.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.FullyAnonymousAuthenticationModule");
|
||||
authenticationTypeToModuleClassNameMap.put(QAuthenticationType.AUTH_0.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule");
|
||||
// todo - let user define custom type -> classes
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void registerModule(String name, String className)
|
||||
{
|
||||
authenticationTypeToModuleClassNameMap.putIfAbsent(name, className);
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication;
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication.implementations;
|
||||
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -45,9 +45,10 @@ import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.auth0.jwt.interfaces.JWTVerifier;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.Auth0AuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.AbstractStateKey;
|
||||
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
@ -93,15 +94,15 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
if(context.containsKey(BASIC_AUTH_KEY))
|
||||
{
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
AuthAPI auth = new AuthAPI(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret());
|
||||
AuthAPI auth = new AuthAPI(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret());
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
// decode the credentials from the header auth //
|
||||
/////////////////////////////////////////////////
|
||||
String base64Credentials = context.get(BASIC_AUTH_KEY).trim();
|
||||
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
|
||||
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
|
||||
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
|
||||
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
|
||||
|
||||
/////////////////////////////////////
|
||||
// call auth0 with a login request //
|
||||
@ -117,11 +118,10 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
////////////////
|
||||
// ¯\_(ツ)_/¯ //
|
||||
////////////////
|
||||
String message = "An unknown error occurred during handling basic auth";
|
||||
String message = "Error handling basic authentication: " + e.getMessage();
|
||||
LOG.error(message, e);
|
||||
throw (new QAuthenticationException(message));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication;
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication.implementations;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
@ -27,6 +27,7 @@ import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication;
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication.implementations;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
@ -27,6 +27,7 @@ import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
@ -0,0 +1,612 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication.implementations;
|
||||
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.TableBasedAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.AbstractStateKey;
|
||||
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TableBasedAuthenticationModule implements QAuthenticationModuleInterface
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(TableBasedAuthenticationModule.class);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 30 minutes - ideally this would be lower, but right now we've been dealing with re-validation issues... //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
public static final int ID_TOKEN_VALIDATION_INTERVAL_SECONDS = 1800;
|
||||
|
||||
public static final String SESSION_ID_KEY = "sessionId";
|
||||
public static final String BASIC_AUTH_KEY = "basicAuthString";
|
||||
|
||||
public static final String SESSION_ID_NOT_PROVIDED_ERROR = "Session ID was not provided";
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this is how we allow the actions within this class to work without themselves having a logged-in user. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private static QSession chickenAndEggSession = new QSession()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QSession createSession(QInstance qInstance, Map<String, String> context) throws QAuthenticationException
|
||||
{
|
||||
TableBasedAuthenticationMetaData metaData = (TableBasedAuthenticationMetaData) qInstance.getAuthentication();
|
||||
String sessionUuid = context.get(SESSION_ID_KEY);
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// check if we are processing a Basic Auth Session first //
|
||||
///////////////////////////////////////////////////////////
|
||||
if(context.containsKey(BASIC_AUTH_KEY))
|
||||
{
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
// decode the credentials from the header auth //
|
||||
/////////////////////////////////////////////////
|
||||
String base64Credentials = context.get(BASIC_AUTH_KEY).trim();
|
||||
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
|
||||
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
|
||||
|
||||
///////////////////////////
|
||||
// fetch the user record //
|
||||
///////////////////////////
|
||||
GetInput getInput = new GetInput(qInstance);
|
||||
getInput.setSession(chickenAndEggSession);
|
||||
getInput.setTableName(metaData.getUserTableName());
|
||||
getInput.setUniqueKey(Map.of(metaData.getUserTableUsernameField(), credentials.split(":")[0]));
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
throw (new QAuthenticationException("Incorrect username or password."));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// compare the hashed input password to the stored hash //
|
||||
//////////////////////////////////////////////////////////
|
||||
QRecord user = getOutput.getRecord();
|
||||
String inputPassword = credentials.split(":")[1];
|
||||
String storedHash = user.getValueString(metaData.getUserTablePasswordHashField());
|
||||
|
||||
// if(!inputHash.equals(storedHash))
|
||||
if(!PasswordHasher.validatePassword(inputPassword, storedHash))
|
||||
{
|
||||
throw (new QAuthenticationException("Incorrect username or password."));
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// insert a session //
|
||||
//////////////////////
|
||||
sessionUuid = UUID.randomUUID().toString();
|
||||
QRecord sessionRecord = new QRecord()
|
||||
.withValue(metaData.getSessionTableUuidField(), sessionUuid)
|
||||
.withValue(metaData.getSessionTableAccessTimestampField(), Instant.now())
|
||||
.withValue(metaData.getSessionTableUserIdField(), user.getValue(metaData.getUserTablePrimaryKeyField()));
|
||||
InsertInput insertInput = new InsertInput(qInstance);
|
||||
insertInput.setSession(chickenAndEggSession);
|
||||
insertInput.setTableName(metaData.getSessionTableName());
|
||||
insertInput.setRecords(List.of(sessionRecord));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
if(CollectionUtils.nullSafeHasContents(insertOutput.getRecords().get(0).getErrors()))
|
||||
{
|
||||
LOG.warn("Inserting session failed: " + insertOutput.getRecords().get(0).getErrors());
|
||||
throw (new QAuthenticationException("Incorrect username or password."));
|
||||
}
|
||||
}
|
||||
catch(QAuthenticationException ae)
|
||||
{
|
||||
// todo - sleep to obscure what was the issue.
|
||||
throw (ae);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
////////////////
|
||||
// ¯\_(ツ)_/¯ //
|
||||
////////////////
|
||||
// todo - sleep to obscure what was the issue.
|
||||
String message = "Error handling basic authentication: " + e.getMessage();
|
||||
LOG.error(message, e);
|
||||
throw (new QAuthenticationException(message));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// get the session uuid from the context object //
|
||||
//////////////////////////////////////////////////
|
||||
if(sessionUuid == null)
|
||||
{
|
||||
LOG.warn(SESSION_ID_NOT_PROVIDED_ERROR);
|
||||
throw (new QAuthenticationException(SESSION_ID_NOT_PROVIDED_ERROR));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////////
|
||||
// try to build session to see if still valid //
|
||||
// then call method to check more session validity //
|
||||
/////////////////////////////////////////////////////
|
||||
QSession qSession = buildQSessionFromUuid(qInstance, metaData, sessionUuid);
|
||||
if(isSessionValid(qInstance, qSession))
|
||||
{
|
||||
return (qSession);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we make it here it means we have never validated this token or its been a long //
|
||||
// enough duration so we need to re-verify the token //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
qSession = revalidateSession(qInstance, sessionUuid);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// put now into state so we dont check until next interval passes //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
StateProviderInterface spi = getStateProvider();
|
||||
SessionIdStateKey key = new SessionIdStateKey(qSession.getIdReference());
|
||||
spi.put(key, Instant.now());
|
||||
|
||||
return (qSession);
|
||||
}
|
||||
catch(QAuthenticationException ae)
|
||||
{
|
||||
LOG.info("Authentication exception", ae);
|
||||
throw (ae);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
////////////////
|
||||
// ¯\_(ツ)_/¯ //
|
||||
////////////////
|
||||
String message = "An unknown error occurred";
|
||||
LOG.error(message, e);
|
||||
throw (new QAuthenticationException(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean isSessionValid(QInstance instance, QSession session)
|
||||
{
|
||||
if(session == chickenAndEggSession)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this is how we allow the actions within this class to work without themselves having a logged-in user. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (true);
|
||||
}
|
||||
|
||||
if(session == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
if(session.getIdReference() == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
StateProviderInterface stateProvider = getStateProvider();
|
||||
SessionIdStateKey key = new SessionIdStateKey(session.getIdReference());
|
||||
Optional<Instant> lastTimeCheckedOptional = stateProvider.get(Instant.class, key);
|
||||
if(lastTimeCheckedOptional.isPresent())
|
||||
{
|
||||
Instant lastTimeChecked = lastTimeCheckedOptional.get();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// returns negative int if less than compared duration, 0 if equal, positive int if greater than //
|
||||
// - so this is basically saying, if the time between the last time we checked the token and //
|
||||
// right now is more than ID_TOKEN_VALIDATION_INTERVAL_SECTIONS, then session needs revalidated //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(Duration.between(lastTimeChecked, Instant.now()).compareTo(Duration.ofSeconds(ID_TOKEN_VALIDATION_INTERVAL_SECONDS)) < 0)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LOG.debug("Re-validating token due to validation interval [" + lastTimeCheckedOptional + "] being passed (or never being set): " + session.getIdReference());
|
||||
revalidateSession(instance, session.getIdReference());
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// update the timestamp in state provider, to avoid re-checking //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
stateProvider.put(key, Instant.now());
|
||||
|
||||
return (true);
|
||||
}
|
||||
catch(QAuthenticationException ae)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error validating session", e);
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** makes request to check if a session is still valid and build new qSession if it is
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QSession revalidateSession(QInstance qInstance, String sessionUuid) throws QException
|
||||
{
|
||||
TableBasedAuthenticationMetaData metaData = (TableBasedAuthenticationMetaData) qInstance.getAuthentication();
|
||||
|
||||
GetInput getSessionInput = new GetInput(qInstance);
|
||||
getSessionInput.setSession(chickenAndEggSession);
|
||||
getSessionInput.setTableName(metaData.getSessionTableName());
|
||||
getSessionInput.setUniqueKey(Map.of(metaData.getSessionTableUuidField(), sessionUuid));
|
||||
GetOutput getSessionOutput = new GetAction().execute(getSessionInput);
|
||||
if(getSessionOutput.getRecord() == null)
|
||||
{
|
||||
throw (new QAuthenticationException("Session not found."));
|
||||
}
|
||||
QRecord sessionRecord = getSessionOutput.getRecord();
|
||||
Instant lastAccess = sessionRecord.getValueInstant(metaData.getSessionTableAccessTimestampField());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// returns negative int if less than compared duration, 0 if equal, positive int if greater than //
|
||||
// - so this is basically saying, if the time between the last time the session was marked as //
|
||||
// active, and right now is more than the timeout seconds, then the session is expired //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(lastAccess.plus(Duration.ofSeconds(metaData.getInactivityTimeoutSeconds())).isBefore(Instant.now()))
|
||||
{
|
||||
throw (new QAuthenticationException("Session is expired."));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// update the timestamp in the session table //
|
||||
///////////////////////////////////////////////
|
||||
UpdateInput updateInput = new UpdateInput(qInstance);
|
||||
updateInput.setSession(chickenAndEggSession);
|
||||
updateInput.setTableName(metaData.getSessionTableName());
|
||||
updateInput.setRecords(List.of(new QRecord()
|
||||
.withValue(metaData.getSessionTablePrimaryKeyField(), sessionRecord.getValue(metaData.getSessionTablePrimaryKeyField()))
|
||||
.withValue(metaData.getSessionTableAccessTimestampField(), Instant.now())));
|
||||
new UpdateAction().execute(updateInput);
|
||||
|
||||
return (buildQSessionFromUuid(qInstance, metaData, sessionUuid));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** extracts info from token creating a QSession
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QSession buildQSessionFromUuid(QInstance qInstance, TableBasedAuthenticationMetaData metaData, String sessionUuid) throws QException
|
||||
{
|
||||
GetInput getSessionInput = new GetInput(qInstance);
|
||||
getSessionInput.setSession(chickenAndEggSession);
|
||||
getSessionInput.setTableName(metaData.getSessionTableName());
|
||||
getSessionInput.setUniqueKey(Map.of(metaData.getSessionTableUuidField(), sessionUuid));
|
||||
GetOutput getSessionOutput = new GetAction().execute(getSessionInput);
|
||||
if(getSessionOutput.getRecord() == null)
|
||||
{
|
||||
throw (new QAuthenticationException("Session not found."));
|
||||
}
|
||||
QRecord sessionRecord = getSessionOutput.getRecord();
|
||||
|
||||
GetInput getUserInput = new GetInput(qInstance);
|
||||
getUserInput.setSession(chickenAndEggSession);
|
||||
getUserInput.setTableName(metaData.getUserTableName());
|
||||
getUserInput.setPrimaryKey(sessionRecord.getValue(metaData.getSessionTableUserIdField()));
|
||||
GetOutput getUserOutput = new GetAction().execute(getUserInput);
|
||||
if(getUserOutput.getRecord() == null)
|
||||
{
|
||||
throw (new QAuthenticationException("User for session not found."));
|
||||
}
|
||||
QRecord userRecord = getUserOutput.getRecord();
|
||||
|
||||
QUser qUser = new QUser();
|
||||
qUser.setFullName(userRecord.getValueString(metaData.getUserTableFullNameField()));
|
||||
qUser.setIdReference(userRecord.getValueString(metaData.getUserTableUsernameField()));
|
||||
|
||||
QSession qSession = new QSession();
|
||||
qSession.setIdReference(sessionUuid);
|
||||
qSession.setUser(qUser);
|
||||
|
||||
return (qSession);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Load an instance of the appropriate state provider
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static StateProviderInterface getStateProvider()
|
||||
{
|
||||
// TODO - read this from somewhere in meta data eh?
|
||||
return (InMemoryStateProvider.getInstance());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class SessionIdStateKey extends AbstractStateKey
|
||||
{
|
||||
private final String key;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor.
|
||||
**
|
||||
*******************************************************************************/
|
||||
SessionIdStateKey(String key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return (this.key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Make the key give a unique string to identify itself.
|
||||
*
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getUniqueIdentifier()
|
||||
{
|
||||
return (this.key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SessionIdStateKey that = (SessionIdStateKey) o;
|
||||
return key.equals(that.key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return key.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class PasswordHasher
|
||||
{
|
||||
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
|
||||
private static final int SALT_BYTE_SIZE = 32;
|
||||
private static final int HASH_BYTE_SIZE = 32;
|
||||
private static final int PBKDF2_ITERATIONS = 1000;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Returns a salted, hashed version of a raw password.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String createHashedPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
{
|
||||
////////////////////////////
|
||||
// Generate a random salt //
|
||||
////////////////////////////
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] salt = new byte[SALT_BYTE_SIZE];
|
||||
random.nextBytes(salt);
|
||||
|
||||
///////////////////////
|
||||
// Hash the password //
|
||||
///////////////////////
|
||||
byte[] passwordHash = computePbkdf2Hash(password.toCharArray(), salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// return string in the format iterations:salt:hash //
|
||||
//////////////////////////////////////////////////////
|
||||
return (PBKDF2_ITERATIONS + ":" + toHex(salt) + ":" + toHex(passwordHash));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Computes the PBKDF2 hash.
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static byte[] computePbkdf2Hash(char[] password, byte[] salt, int iterations, int bytes) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
{
|
||||
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
|
||||
|
||||
return skf.generateSecret(spec).getEncoded();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Thanks to Baeldung for this and related methods
|
||||
** https://www.baeldung.com/java-byte-arrays-hex-strings
|
||||
*******************************************************************************/
|
||||
private static String toHex(byte[] array)
|
||||
{
|
||||
StringBuilder hexStringBuffer = new StringBuilder();
|
||||
for(byte b : array)
|
||||
{
|
||||
hexStringBuffer.append(Character.forDigit((b >> 4) & 0xF, 16));
|
||||
hexStringBuffer.append(Character.forDigit((b & 0xF), 16));
|
||||
}
|
||||
return hexStringBuffer.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Validates a password against a hash.
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static boolean validatePassword(String password, String passwordHash) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
{
|
||||
String[] params = passwordHash.split(":");
|
||||
int iterations = Integer.parseInt(params[0]);
|
||||
byte[] salt = fromHex(params[1]);
|
||||
byte[] hash = fromHex(params[2]);
|
||||
|
||||
byte[] testHash = computePbkdf2Hash(password.toCharArray(), salt, iterations, hash.length);
|
||||
return slowEquals(hash, testHash);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Compares two byte arrays in length-constant time. This comparison method
|
||||
** is used so that password hashes cannot be extracted from an on-line
|
||||
** system using a timing attack and then attacked off-line.
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static boolean slowEquals(byte[] a, byte[] b)
|
||||
{
|
||||
int diff = a.length ^ b.length;
|
||||
|
||||
for(int i = 0; i < a.length && i < b.length; i++)
|
||||
{
|
||||
diff |= a[i] ^ b[i];
|
||||
}
|
||||
|
||||
return diff == 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static int toHexDigit(char hexChar)
|
||||
{
|
||||
int digit = Character.digit(hexChar, 16);
|
||||
if(digit == -1)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid Hexadecimal Character: " + hexChar);
|
||||
}
|
||||
return digit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static byte[] fromHex(String hexString)
|
||||
{
|
||||
if(hexString.length() % 2 == 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid hexadecimal String supplied.");
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[hexString.length() / 2];
|
||||
for(int i = 0; i < hexString.length(); i += 2)
|
||||
{
|
||||
int firstDigit = toHexDigit(hexString.charAt(i));
|
||||
int secondDigit = toHexDigit(hexString.charAt(i + 1));
|
||||
bytes[i / 2] = (byte) ((firstDigit << 4) + secondDigit);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -74,6 +74,18 @@ public class MemoryRecordStore
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** forget all data AND statistics
|
||||
*******************************************************************************/
|
||||
public static void fullReset()
|
||||
{
|
||||
getInstance().reset();
|
||||
resetStatistics();
|
||||
setCollectStatistics(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Forget all data in the memory store...
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user