I might be misunderstanding the intended behavior of this method, but this is what I am trying to use it for:
-User logs in successfully
-User closes app completely (closes in background as well)
-User opens app again and doesn't have to log in again because CognitoCachingCredentialsProvider can check locally on the device to see she's still logged in
The way I tried to accomplish this is to check, before being prompted to log in, what getCachedIdentityId() returns. If it returns not null, then that means that she's still logged in, because there was nothing that cleared her credentials from the device. Here's what my framework looks like. I'm using developer authenticated method:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.util.Log;
import com.amazonaws.auth.AWSAbstractCognitoIdentityProvider;
import com.amazonaws.auth.CognitoCachingCredentialsProvider;
import com.amazonaws.mobileconnectors.cognito.*;
import com.amazonaws.regions.Regions;
public class Util {
private final static String TAG = "Util";
private static final String AWS_ACCOUNT_ID = {acct id};
private static final String COGNITO_POOL_ID = {pool id};
private static final String COGNITO_ROLE_AUTH = {auth arn};
private static final String COGNITO_ROLE_UNAUTH = {unauth arn}
private static CognitoCachingCredentialsProvider sCredProvider;
private static UserIdentityProvider sIdProvider;
private static CognitoSyncManager sSyncManager;
private Util() {
}
public static CognitoCachingCredentialsProvider getCredProvider(
Context context) {
if (sCredProvider == null) {
if (sIdProvider == null) {
CognitoCachingCredentialsProvider tmpProvider = new CognitoCachingCredentialsProvider(
context.getApplicationContext(), AWS_ACCOUNT_ID,
COGNITO_POOL_ID, COGNITO_ROLE_UNAUTH,
COGNITO_ROLE_AUTH, Regions.US_EAST_1);
if (tmpProvider.getCachedIdentityId() != null) {
sCredProvider = tmpProvider;
} else {
sCredProvider = null;
}
} else {
sCredProvider = new CognitoCachingCredentialsProvider(
context.getApplicationContext(), sIdProvider,
COGNITO_ROLE_UNAUTH, COGNITO_ROLE_AUTH);
Map logins = new HashMap();
logins.put({Developer Provider Name}, sIdProvider.getToken());
sCredProvider.setLogins(logins);
}
}
return sCredProvider;
}
public static UserIdentityProvider getIdentityProvider(Context context,
String email, String pwd) {
if (sIdProvider == null) {
sIdProvider = new UserIdentityProvider(AWS_ACCOUNT_ID,
COGNITO_POOL_ID, context.getApplicationContext());
}
return sIdProvider;
}
public static boolean isLoggedIn(Context context) {
if (getCredProvider(context) == null) {
return false;
}
return true;
}
protected static class UserIdentityProvider extends
AWSAbstractCognitoIdentityProvider {
private Context context;
private String email;
private String password;
public UserIdentityProvider(String accountId, String identityPoolId,
Context c) {
super(accountId, identityPoolId);
context = c;
email = em;
password = pwd;
}
#Override
public String refresh() {
try {
ServerCommunicator server = new ServerCommunicator(context);
if (email != null && password != null) {
//this is a server call, which makes the call GetOpenIdTokenForDeveloperIdentityRequest after I authenticate the user and send AWS my user's token
String response = server.initUserLoginAsyncTask()
.execute(email, password).get();
prefs.setAllUserSharedPrefs(response);
JSONObject responseJSON = new JSONObject(response);
String identityId = responseJSON.getString("id");
String token = responseJSON.getString("token");
if (token != null && identityId != null) {
this.setToken(token);
this.setIdentityId(identityId);
update(identityId, token);
return token;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
#Override
public String getProviderName() {
return {Developer Provider Name};
}
}
}
I simply call the isLoggedIn method from this class to see if there's an IdentityId stored locally. However, this isn't working as expected. I can see from debugging that getCachedIdentityId is always null (even directly after initializing CognitoCachingCredentialsProvider and adding the token to the logins map) and I am always prompted to log in again whenever I open the app after it has been closed. When does the IdentityId actually get stored locally and is my logic correct in general?
Additional Code
import java.util.concurrent.ExecutionException;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class LoginActivity extends Activity {
private final String TAG = "LoginActivity";
private EditText etEmail, etPwd;
private Button bLogin, bGoToRegister;
private ServerCommunicator server;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate");
server = new ServerCommunicator(this);
if (Util.isLoggedIn(this)) {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
return;
}
this.setContentView(R.layout.activity_login);
etEmail = (EditText) findViewById(R.id.etEmail);
etPwd = (EditText) findViewById(R.id.etPassword);
bLogin = (Button) findViewById(R.id.bLogin);
bGoToRegister = (Button) findViewById(R.id.bGoToRegister);
bLogin.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
String email = etEmail.getText().toString();
String pwd = etPwd.getText().toString();
Util.getIdentityProvider(v.getContext()).setEmail(email);
Util.getIdentityProvider(v.getContext()).setPassword(pwd);
String token = Util.getIdentityProvider(v.getContext()).refresh();
if (token != null) {
Intent intent = new Intent(v.getContext(), MainActivity.class);
startActivity(intent);
} else {
Toast.makeText(v.getContext(), "Invalid username/password",
Toast.LENGTH_SHORT).show();
}
}
});
}
}
The above is my LoginActivity. When the app starts the MainActivity, I have the following snippet at the beginning of my onCreate method:
if (!Util.isLoggedIn(this)) {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
This call initializes the CognitoCachingCredentialsProvider. I assumed that this would be when the IdentityId gets cached, but my debugging has revealed that even directly after this block, getCachedIdentityId() still returns null. Am I way off base with how I'm trying to use this class?
I have one suggestion. The CognitoCachingCredentialsProvider is what saves the identityId as it's changed. It doesn't start listening, though, until it's been initialized, and the change occurs on refresh call to your identity provider.
Can you try moving the initialization of the CognitoCachingCredentialsProvider to before the refresh call (but after your identity provider initialization)?
Edit:
Update will set the identityId and token, however explicit calls made just beforehand may cause it to think no change is being made. Can you try eliminating the setter calls as well?
Related
I am using wso2 IS 5.10, for adding custom claim which needs to be added by fetching from db I am using custom adaptive function. But the below code is not working.
package org.wso2.custom.auth.functions;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.authentication.framework.config.model.graph.js.JsAuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.config.model.graph.js.JsAuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import org.wso2.custom.auth.functions.internal.CustomAuthFuncComponent;
import org.wso2.carbon.identity.application.authentication.framework.config.model.graph.js.*;
public class SetForceAuthFunctionImpl implements SetForceAuthFunction {
private static final Log LOGGER = LogFactory.getLog(SetForceAuthFunctionImpl.class);
#Override
public JsAuthenticatedUser setForceAuth(JsAuthenticationContext context, boolean forceAuth) {
AuthenticatedUser lastAuthenticatedUser = context.getContext().getLastAuthenticatedUser();
LOGGER.info("lastAuthenticatedUser****:::::::::::"+lastAuthenticatedUser);
String userName = lastAuthenticatedUser.getUserName();
LOGGER.info("userName2****:::::::::::"+userName);
String tenantDomain = MultitenantUtils.getTenantDomain(userName);
String fullyQualifiedUserName=("USERS"+"/"+userName+"#"+tenantDomain);
Map<org.wso2.carbon.identity.application.common.model.ClaimMapping, String> claims = new HashMap<org.wso2.carbon.identity.application.common.model.ClaimMapping, String>();
claims.put(org.wso2.carbon.identity.application.common.model.ClaimMapping.build("test123", "test123", null, true), org.apache.commons.lang3.StringUtils.join("*******************",",,,"));
AuthenticatedUser authenticatedUserObj = AuthenticatedUser.createLocalAuthenticatedUserFromSubjectIdentifier(MultitenantUtils.getTenantAwareUsername
(fullyQualifiedUserName));
authenticatedUserObj.setAuthenticatedSubjectIdentifier(MultitenantUtils.getTenantAwareUsername
(fullyQualifiedUserName));
authenticatedUserObj.setUserAttributes(claims);
authenticatedUserObj.setUserName(MultitenantUtils.getTenantAwareUsername
(fullyQualifiedUserName));
return new JsAuthenticatedUser(authenticatedUserObj);
}
}
package org.wso2.custom.auth.functions.internal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.wso2.carbon.identity.application.authentication.framework.JsFunctionRegistry;
import org.wso2.carbon.registry.core.service.RegistryService;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.custom.auth.functions.GenerateHashFunction;
import org.wso2.custom.auth.functions.GenerateHashFunctionImpl;
import org.wso2.custom.auth.functions.GetClaimsForUsernameFunction;
import org.wso2.custom.auth.functions.GetClaimsForUsernameFunctionImpl;
import org.wso2.custom.auth.functions.GetUsernameFromContextFunction;
import org.wso2.custom.auth.functions.GetUsernameFromContextFunctionImpl;
import org.wso2.custom.auth.functions.SetForceAuthFunction;
import org.wso2.custom.auth.functions.SetForceAuthFunctionImpl;
#Component(
name = "custom.auth.functions.component",
immediate = true
)
public class CustomAuthFuncComponent {
private static final Log LOG = LogFactory.getLog(CustomAuthFuncComponent.class);
private static JsFunctionRegistry jsFunctionRegistry;
#Activate
protected void activate(ComponentContext ctxt) {
SetForceAuthFunction setForceAuthFunctionImpl = new SetForceAuthFunctionImpl();
jsFunctionRegistry.register(JsFunctionRegistry.Subsystem.SEQUENCE_HANDLER, "setForceAuth",
setForceAuthFunctionImpl);
GetUsernameFromContextFunction getUsernameFromContextFunctionImpl = new GetUsernameFromContextFunctionImpl();
jsFunctionRegistry.register(JsFunctionRegistry.Subsystem.SEQUENCE_HANDLER, "getUsernameFromContext",
getUsernameFromContextFunctionImpl);
GetClaimsForUsernameFunction getClaimsForUsernameFunctionImpl = new GetClaimsForUsernameFunctionImpl();
jsFunctionRegistry.register(JsFunctionRegistry.Subsystem.SEQUENCE_HANDLER, "getClaimsForUsername",
getClaimsForUsernameFunctionImpl);
GenerateHashFunction generateHashFunctionImpl = new GenerateHashFunctionImpl();
jsFunctionRegistry.register(JsFunctionRegistry.Subsystem.SEQUENCE_HANDLER, "generateHash",
generateHashFunctionImpl);
}
#Deactivate
protected void deactivate(ComponentContext ctxt) {
if (jsFunctionRegistry != null) {
jsFunctionRegistry.deRegister(JsFunctionRegistry.Subsystem.SEQUENCE_HANDLER, "setForceAuth");
jsFunctionRegistry.deRegister(JsFunctionRegistry.Subsystem.SEQUENCE_HANDLER, "getUsernameFromContext");
jsFunctionRegistry.deRegister(JsFunctionRegistry.Subsystem.SEQUENCE_HANDLER, "getClaimsForUsername");
jsFunctionRegistry.deRegister(JsFunctionRegistry.Subsystem.SEQUENCE_HANDLER, "generateHash");
}
}
#Reference(
name = "user.realmservice.default",
service = RealmService.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetRealmService"
)
protected void setRealmService(RealmService realmService) {
if (LOG.isDebugEnabled()) {
LOG.debug("RealmService is set in the custom conditional authentication user functions bundle");
}
CustomAuthFuncHolder.getInstance().setRealmService(realmService);
}
protected void unsetRealmService(RealmService realmService) {
if (LOG.isDebugEnabled()) {
LOG.debug("RealmService is unset in the custom conditional authentication user functions bundle");
}
CustomAuthFuncHolder.getInstance().setRealmService(null);
}
#Reference(
name = "registry.service",
service = RegistryService.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetRegistryService"
)
protected void setRegistryService(RegistryService registryService) {
if (LOG.isDebugEnabled()) {
LOG.debug("RegistryService is set in the custom conditional authentication user functions bundle");
}
CustomAuthFuncHolder.getInstance().setRegistryService(registryService);
}
protected void unsetRegistryService(RegistryService registryService) {
if (LOG.isDebugEnabled()) {
LOG.debug("RegistryService is unset in the custom conditional authentication user functions bundle");
}
CustomAuthFuncHolder.getInstance().setRegistryService(null);
}
#Reference(
service = JsFunctionRegistry.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetJsFunctionRegistry"
)
public void setJsFunctionRegistry(JsFunctionRegistry jsFunctionRegistry) {
this.jsFunctionRegistry = jsFunctionRegistry;
}
public void unsetJsFunctionRegistry(JsFunctionRegistry jsFunctionRegistry) {
this.jsFunctionRegistry = null;
}
}
But when I am using setForceAuth(context, true); in adaptive authentication function to add custom claims its not working but working in custom authenticator.
Adaptive authentication script:
function onLoginRequest(context) {
doLogin(context);
}
function doLogin(context) {
executeStep(1,{
onSuccess: function (context) {
},
onFail: function(context){
executeStep(4,{
onSuccess: function (context) {
var subject = context.currentKnownSubject;
setForceAuth(context, true);
},
onFail: function(context){
}
});
}
});
}
The issue is that
setForceAuth(context, true);
executes the below code block
return new JsAuthenticatedUser(authenticatedUserObj);
There is no point in your code (authencation script or the java function) where the newly created JsAuthenticatedUser is set to the context.
What you need to do is to change the script like this
onSuccess: function (context) { context.currentKnownSubject = setForceAuth(context, true);}
or
the java function as below
`#Override
public JsAuthenticatedUser setForceAuth(JsAuthenticationContext context, boolean forceAuth) {
AuthenticatedUser lastAuthenticatedUser = context.getContext().getLastAuthenticatedUser();
LOGGER.info("lastAuthenticatedUser****:::::::::::"+lastAuthenticatedUser);
String userName = lastAuthenticatedUser.getUserName();
LOGGER.info("userName2****:::::::::::"+userName);
String tenantDomain = MultitenantUtils.getTenantDomain(userName);
String fullyQualifiedUserName=("USERS"+"/"+userName+"#"+tenantDomain);
Map<org.wso2.carbon.identity.application.common.model.ClaimMapping, String> claims = new HashMap<org.wso2.carbon.identity.application.common.model.ClaimMapping, String>();
claims.put(org.wso2.carbon.identity.application.common.model.ClaimMapping.build("test123", "test123", null, true), org.apache.commons.lang3.StringUtils.join("*******************",",,,"));
AuthenticatedUser authenticatedUserObj = AuthenticatedUser.createLocalAuthenticatedUserFromSubjectIdentifier(MultitenantUtils.getTenantAwareUsername
(fullyQualifiedUserName));
authenticatedUserObj.setAuthenticatedSubjectIdentifier(MultitenantUtils.getTenantAwareUsername
(fullyQualifiedUserName));
authenticatedUserObj.setUserAttributes(claims);
authenticatedUserObj.setUserName(MultitenantUtils.getTenantAwareUsername
(fullyQualifiedUserName));
context.getContext().setSubject(authenticatedUserObj);
}
Still it is not advisable to have a tenant aware user name on authentication scripts. Rather can you think of not using
setForceAuth()
function and use the provided user.localClaims[] instead?
I was trying to mock a private method's output which is being called inside another private method, I have no choice but to test the later private method, so I have added sample test code which I can represent here,
This Sample Class
package com.testableClass;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class TestableClass {
private int initialMockMethod(Object obj)
{
System.out.println(" ++++ Came Here ++++ ");
String str = getRestString("");
System.out.println("str ="+str);
return str.length();
}
private String getRestString(String abc)
{
String output="";
try {
URL url = new URL("https://gorest.co.in/public-api/users");//your url i.e fetch data from .
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP Error code : "
+ conn.getResponseCode());
}
InputStreamReader in = new InputStreamReader(conn.getInputStream());
BufferedReader br = new BufferedReader(in);
while ((output = br.readLine()) != null) {
System.out.println(output);
}
conn.disconnect();
return output;
} catch (Exception e) {
System.out.println("Exception in NetClientGet:- " + e);
}
return abc;
}
}
Now This PowerMock Class
package com.testableClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import junit.framework.Assert;
#RunWith(PowerMockRunner.class)
#PrepareForTest(fullyQualifiedNames = "com.testableClass.TestableClass")
public class PowerMockTest {
#Test
public void testPrivateMethod() throws Exception
{
String message = "Hello PowerMockito";
String expectation = "Expectation";
TestableClass mock = PowerMockito.spy(new TestableClass());
// PowerMockito.doReturn(expectation).when(mock, "getRestString", message);
PowerMockito.when(mock, "getRestString", message).thenReturn(expectation);
int count = Whitebox.invokeMethod(mock, "initialMockMethod", new Object());
System.out.println(" +++ Count : "+count+" ++++ ");
Assert.assertTrue(true);
}
}
my issue is when I am running my test case then
PowerMockito.when(mock, "getRestString", message).thenReturn(expectation);
executes original method and returns original output while my requirement is that,
when my test case is actually calling private method initialMockMethod it should not call getRestString instead of that it should return my mocked expected output which is "Expectation"
Instead of using reflection and PowerMock, I'd say that do not try to mock a private method. It is a hidden detail of the class.
What I suggest is that, if your method makes an HTTP request, then let it do that. But you can use a mock server to mock the response. But to do that, you need to make your endpoint external, so that it can be injected.
I changed your class a little bit; still the same purpose tho.
public class TestableClass {
private final String resourceUrl;
public TestableClass(String resourceUrl) {
this.resourceUrl = resourceUrl;
}
public int publicMethod() {
return initialMockMethod(null);
}
private int initialMockMethod(Object obj) {
var str = getRestString("");
return str.length();
}
private String getRestString(String abc) {
try {
var url = new URL(resourceUrl);
var conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP Error code : "
+ conn.getResponseCode());
}
var in = new InputStreamReader(conn.getInputStream());
var br = new BufferedReader(in);
var result = br.lines().collect(Collectors.joining("\n"));
conn.disconnect();
return result;
} catch (Exception e) {
System.out.println("Exception in NetClientGet:- " + e);
}
return abc;
}
}
and here's the test
public class TestingTestableClass {
#Test
#SneakyThrows
public void test() {
final var server = new MockWebServer();
server.start(9999);
var instance = new TestableClass("http://127.0.0.1:9999");
server.enqueue(
new MockResponse()
.setResponseCode(200)
.setBody("this is the response")
);
final int result = instance.publicMethod();
Assertions.assertEquals(
"this is the response".length(),
result
);
final var record = server.takeRequest();
final var method = record.getMethod();
Assertions.assertEquals("GET", method);
server.shutdown();
}
}
Check out MockWebServer here
I'm trying to create and test an API endpoint using AWS Lambda and API Gateway. I can test my function successfully using Lambda Test, but when I try to test my endpoint it gives:
{
"message": "Internal server error"
}
This is my handler class:
package com.amazonaws.lambda.gandhi.conversion.api;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.RandomStringUtils;
import com.amazonaws.lambda.gandhi.conversion.api.Response.AuthClientCredentialResponse;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.lambda.gandhi.conversion.api.utils.ClientAuthPOJO;
public class AuthClientCredentialServiceHandler implements RequestHandler<ClientAuthPOJO, Object> {
private AuthClientCredentialResponse authClientCredentialResponse;
private static final SecureRandom RANDOM = new SecureRandom();
public static int MAX_CLIENT_KEY = 10;
public static int CLIENT_SECRET_LENGTH = 69;
#Override
public AuthClientCredentialResponse handleRequest(ClientAuthPOJO clientIdSecret, Context context) {
String clientSecret;
try {
context.getLogger().log("Input: "
+ clientIdSecret);
String clientId = clientIdSecret.getClientId();
clientSecret = generateClientSecretKey();
Map<String, String> clientCredsMap = getClientCredentials();
if (clientCredsMap.size() > MAX_CLIENT_KEY) {
throw new RuntimeException(String.format("Max limit is %d, Please delete some keys", MAX_CLIENT_KEY));
}
clientCredsMap.forEach((k, v) -> {
if (clientId.equals(k)) {
throw new RuntimeException("Client Already exists");
}
});
storeClientCredentials(clientId, clientSecret);
AuthClientCredentialResponse authClientCredentialResponse = AuthClientCredentialResponse.builder().success(
true).clientId(clientId).clientSecret(clientSecret).build();
this.authClientCredentialResponse = authClientCredentialResponse;
} catch (Exception e) {
throw new RuntimeException(
"Failed to generate client secret: "
+ e.getMessage());
}
return authClientCredentialResponse;
}
private String generateClientSecretKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
String clientSecret = RandomStringUtils.randomAlphanumeric(CLIENT_SECRET_LENGTH);
System.out.printf("clientSecret: %s%n", clientSecret);
return clientSecret;
}
private void storeClientCredentials(String clientId, String clientSecret) throws IOException {
/*
* TODO:
* Some logic to store clientCredentials to a file or DB. Decide later.
*/
System.out.println("temp ClientCredentials stored");
}
public Map<String, String> getClientCredentials() throws IOException {
/*
* TODO:
* Some logic to fetch clientCredentials from file or DB. Decide later.
*/
Map<String, String> clientCredMap = new HashMap<String, String>();
clientCredMap.put("1", "secretKey1");
clientCredMap.put("2", "secretKey2");
clientCredMap.put("3", "secretKey3");
clientCredMap.put("4", "secretKey4");
return clientCredMap;
}
}
My input class:
package com.amazonaws.lambda.gandhi.conversion.api.utils;
public class ClientAuthPOJO {
String clientId;
String clientSecret;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public ClientAuthPOJO(String clientId, String clientSecret) {
super();
this.clientId = clientId;
this.clientSecret = clientSecret;
}
public ClientAuthPOJO() {
}
}
My test object in lambda:
My test for endpoint in API Gateway:
Can someone please help me figure out the problem in creating the function or API Gateway?
Edit:
When I check the logs, I found that the parameters to the functions (clientId and clientSecret) are null. So there seems to be some problem in the way I'm sending my request body.
My organization is running spring boot app on AWS ECS docker container which reads the credentials for Postgres sql from secrets manager in AWS during boot up. AS part of security complaince, we are rotating the secrets every 3 months. The spring boot app is loosing connection with the database and going down when the RDS credentials are rotated.we have to restart it in order to pick the new credentials to work properly. Is there any way I can read the credentials automatically once the credentials are rotated to avoid restarting the application manually?
After some research I found that the postgres database in AWS supports passwordless authentication using IAM roles. We can generate a token which is valid for 15 mins and can connect to database using that token. I prefer this way of connecting to database rather than using password for my database. More details about setting up password less authentication can be found here
Code example as below
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.rds.auth.GetIamAuthTokenRequest;
import com.amazonaws.services.rds.auth.RdsIamAuthTokenGenerator;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.Properties;
public class RdsIamAuthDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
private static final Logger LOGGER = LoggerFactory.getLogger(RdsIamAuthDataSource.class);
private static final int DEFAULT_PORT = 5432;
private static final String USESSL = "useSSL";
private static final String REQUIRE_SSL = "requireSSL";
private static final String BOOLEAN_TRUE = "true";
private static final String VERIFY_SERVER_CERTIFICATE = "verifyServerCertificate";
private static final String THREAD_NAME = "RdsIamAuthDataSourceTokenThread";
/**
* Constructor for RdsIamAuthDataSource.
* #param props {#link PoolConfiguration}
*/
public RdsIamAuthDataSource(PoolConfiguration props) {
this.poolProperties = props;
}
#Override
public ConnectionPool createPool() throws SQLException {
if (pool == null) {
return createPoolImpl();
} else {
return pool;
}
}
protected ConnectionPool createPoolImpl() throws SQLException {
synchronized (this) {
return pool = new RdsIamAuthConnectionPool(poolProperties);
}
}
private class RdsIamAuthConnectionPool extends ConnectionPool implements Runnable {
private RdsIamAuthTokenGenerator rdsIamAuthTokenGenerator;
private String host;
private String region;
private int port;
private String username;
private Thread tokenThread;
/**
* Constructor for RdsIamAuthConnectionPool.
* #param prop {#link PoolConfiguration}
* #throws SQLException {#link SQLException}
*/
public RdsIamAuthConnectionPool(PoolConfiguration prop) throws SQLException {
super(prop);
}
#Override
protected void init(PoolConfiguration prop) throws SQLException {
try {
final URI uri = new URI(prop.getUrl().substring(5));
this.host = uri.getHost();
this.port = uri.getPort();
if (this.port < 0) {
this.port = DEFAULT_PORT;
}
this.region = StringUtils.split(this.host,'.')[2];
this.username = prop.getUsername();
this.rdsIamAuthTokenGenerator = RdsIamAuthTokenGenerator.builder()
.credentials(new DefaultAWSCredentialsProviderChain())
.region(this.region)
.build();
updatePassword(prop);
final Properties props = prop.getDbProperties();
props.setProperty(USESSL, BOOLEAN_TRUE);
props.setProperty(REQUIRE_SSL, BOOLEAN_TRUE);
props.setProperty(VERIFY_SERVER_CERTIFICATE, BOOLEAN_TRUE);
super.init(prop);
this.tokenThread = new Thread(this, THREAD_NAME);
this.tokenThread.setDaemon(true);
this.tokenThread.start();
} catch (URISyntaxException e) {
LOGGER.error("Database URL is not correct. Please verify", e);
throw new RuntimeException(e.getMessage());
}
}
/**
* Refresh the token every 12 minutes.
*/
#Override
public void run() {
try {
while (this.tokenThread != null) {
Thread.sleep(12 * 60 * 1000);
updatePassword(getPoolProperties());
}
} catch (InterruptedException e) {
LOGGER.error("Background token thread interrupted", e);
}
}
#Override
protected void close(boolean force) {
super.close(force);
final Thread thread = tokenThread;
if (thread != null) {
thread.interrupt();
}
}
private void updatePassword(PoolConfiguration props) {
final String token = rdsIamAuthTokenGenerator.getAuthToken(GetIamAuthTokenRequest.builder()
.hostname(host)
.port(port)
.userName(this.username)
.build());
LOGGER.info("Updated IAM token for connection pool");
props.setPassword(token);
}
}
}
Supply the following DataSource as a spring bean. That's it. Now your application will automatically refresh credentials every 12 minutes
#Bean
public DataSource dataSource() {
final PoolConfiguration props = new PoolProperties();
props.setUrl("jdbc:postgresql://myapp.us-east-2.rds.amazonaws.com/myschema?ssl=true");
props.setUsername("rdsadminuser");
props.setDriverClassName("org.somedatabase.Driver");
return new RdsIamAuthDataSource(props);
}
I'm using the developer-authenticated technique for implementing this class, as described here. So far, I've been able to implement this class and build a framework in which I check CognitoCachingCredentialsProvider.getCachedIdentityId() to see if a user has logged in (and therefore doesn't need to re-authenticate by entering an email and password). To do this, I'm using a series of static methods in a class called Util, since these only need to be instantiated once. This is what it looks like:
package com.pranskee.boxesapp;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.util.Log;
import com.amazonaws.auth.AWSAbstractCognitoIdentityProvider;
import com.amazonaws.auth.CognitoCachingCredentialsProvider;
import com.amazonaws.mobileconnectors.cognito.*;
import com.amazonaws.regions.Regions;
public class Util {
private final static String TAG = "Util";
private static final String AWS_ACCOUNT_ID = {acct id};
private static final String COGNITO_POOL_ID = {pool id};
private static final String COGNITO_ROLE_AUTH = {auth arn};
private static final String COGNITO_ROLE_UNAUTH = {unauth arn}
private static CognitoCachingCredentialsProvider sCredProvider;
private static UserIdentityProvider sIdProvider;
private static CognitoSyncManager sSyncManager;
private Util() {
}
public static CognitoCachingCredentialsProvider getCredProvider(
Context context) {
if (sCredProvider == null) {
if (sIdProvider == null) {
CognitoCachingCredentialsProvider tmpProvider = new CognitoCachingCredentialsProvider(
context.getApplicationContext(), AWS_ACCOUNT_ID,
COGNITO_POOL_ID, COGNITO_ROLE_UNAUTH,
COGNITO_ROLE_AUTH, Regions.US_EAST_1);
if (tmpProvider.getCachedIdentityId() != null) {
sCredProvider = tmpProvider;
} else {
sCredProvider = null;
}
} else {
sCredProvider = new CognitoCachingCredentialsProvider(
context.getApplicationContext(), sIdProvider,
COGNITO_ROLE_UNAUTH, COGNITO_ROLE_AUTH);
}
}
return sCredProvider;
}
public static UserIdentityProvider getIdentityProvider(Context context,
String email, String pwd) {
if (sIdProvider == null) {
sIdProvider = new UserIdentityProvider(AWS_ACCOUNT_ID,
COGNITO_POOL_ID, context.getApplicationContext(), email,
pwd);
Map logins = new HashMap();
logins.put({Developer Provider Name}, sIdProvider.getToken());
sIdProvider.setLogins(logins);
}
return sIdProvider;
}
public static boolean isLoggedIn(Context context) {
if (getCredProvider(context) == null) {
return false;
}
return true;
}
private static CognitoSyncManager getSyncManager(Context context) {
if (sSyncManager == null) {
sSyncManager = new CognitoSyncManager(
context.getApplicationContext(), Regions.US_EAST_1,
sCredProvider);
}
return sSyncManager;
}
protected static class UserIdentityProvider extends
AWSAbstractCognitoIdentityProvider {
private Context context;
private String email;
private String password;
public UserIdentityProvider(String accountId, String identityPoolId,
Context c, String em, String pwd) {
super(accountId, identityPoolId);
context = c;
email = em;
password = pwd;
}
#Override
public String refresh() {
try {
ServerCommunicator server = new ServerCommunicator(context);
//this is a server call, which makes the call GetOpenIdTokenForDeveloperIdentityRequest after I authenticate the user and send AWS my user's token
String response = server.initUserLoginAsyncTask()
.execute(email, password).get();
JSONObject responseJSON = new JSONObject(response);
String identityId = responseJSON.getString("id");
String token = responseJSON.getString("token");
this.setToken(token);
this.setIdentityId(identityId);
update(identityId, token);
return token;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
#Override
public String getProviderName() {
return {Developer Provider Name};
}
}
}
Now, I want to also implement a Logout. I think what I'd need to do is remove the cached Identity Id somehow, but I'm not sure what the best method would be to do that. Or, maybe it's not that at all, and I need to do something differently entirely. Either way, I just want to implement the intended behavior of allowing a user to select to "Log Out" of my app, which causes Cognito to forget that that ID was logged into the Identity Pool and invalidates any attempt to establish an Identity ID again without going through my authentication process again.
Logout would be a two steps process, first you need to logout from the Identity Provider that authenticated your user (Amazon, Google, Facebook or your own) Instructions on how to do this are specific to your provider.
From the CognitoIdentity side, you need to tell the CredentialsProvider to clear all state and cache associated with your identity. Using Android SDK, you can call clear() on the CredentialsProvider (see http://docs.aws.amazon.com/AWSAndroidSDK/latest/javadoc/com/amazonaws/auth/CognitoCredentialsProvider.html)