Get AWS Cognito user ID in Serverless Java function - java

I am following the https://serverless-stack.com/ tutorial which uses the Serverless framework to create an API that inserts objects into a DynamoDB table and associates them to the authenticated AWS Cognito user. I am attempting to convert the Node.js code to Java but I have hit a problem when getting the Cognito identity as shown on this page
userId: event.requestContext.identity.cognitoIdentityId,
I expected the following lines of Java code to be equivalent:
final CognitoIdentity identity = context.getIdentity();
final String userId = identity.getIdentityId();
but userId is empty.
I am using the aws-api-gateway-cli-test utility to call my API with credentials of a Cognito user as shown on this page. The authentication passes but the userId is empty in the handler.
This is my function:
package com.mealplanner.function;
import java.util.Map;
import com.amazonaws.services.lambda.runtime.CognitoIdentity;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mealplanner.dal.MealRepository;
import com.mealplanner.domain.Meal;
import com.serverless.ApiGatewayResponse;
public class CreateMealHandler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
#Override
public ApiGatewayResponse handleRequest(final Map<String, Object> request, final Context context) {
try {
final CognitoIdentity identity = context.getIdentity();
final String userId = identity.getIdentityId();
final JsonNode body = new ObjectMapper().readTree((String) request.get("body"));
final MealRepository repository = new MealRepository();
final Meal meal = new Meal();
meal.setUserId(userId);
meal.setDescription(body.get("description").asText());
repository.save(meal);
return ApiGatewayResponse.builder()
.setStatusCode(200)
.setObjectBody(meal)
.build();
} catch (final Exception e) {
final String errorText = String.format("Error saving meal with request [%s]", request);
LOGGER.error(errorText, e);
return ApiGatewayResponse.builder()
.setStatusCode(500)
.setObjectBody(errorText)
.build();
}
}
}
And this is the function definition in serverless.yml:
createMeal:
handler: com.mealplanner.function.CreateMealHandler
events:
- http:
path: /meals
method: post
cors: true
authorizer: aws_iam
Am I missing some configuration or have I not translated the Node.js code correctly?
In case I have missed any pertinent information the full code is available here: https://github.com/stuartleylandcole/meal-planner/tree/add-users. I will update this question with anything that is missing to ensure all relevant information is self-contained.

It turns out I hadn't translated the Node.js code correctly. To access the CognitoIdentityId I had to get the requestContext from the request object, then get the identity object, like so:
public ApiGatewayResponse handleRequest(final Map<String, Object> request, final Context context) {
final Map<String, Object> requestContext = (Map<String, Object>) request.get("requestContext");
final Map<String, Object> identity = (Map<String, Object>) requestContext.get("identity");
final String userId = (String) identity.get("cognitoIdentityId");
// etc
}

Related

Read and write Email using Microsoft Graph API Java

I want to read and send email using Microsoft Graph API. I have tried using DeviceCodeCredentials and it is working fine but I want to read emails without any user interaction in the backend. By Using, DeviceCodeCredentials it asks me to login using my email id and enter the code provided.
below I the code I have written with ClientSecretCredentials which give me below error.
Exception in thread "main" java.lang.RuntimeException: Unable to get access token
at com.eclerx.email.AccessProvider.accessToken(AccessProvider.java:30)
at com.eclerx.email.AuthenticationBuilder2.main(AuthenticationBuilder2.java:55)
Caused by: java.util.concurrent.ExecutionException:
com.microsoft.aad.msal4j.MsalServiceException: AADSTS1002012: The provided value for scope
User.read openid profile offline_access Mail.Read is not valid. Client credential flows must
have a scope value with /.default suffixed to the resource identifier (application ID URI).
Trace ID: ac0c217d-72c7-4ba0-9472-16c711ceea00
Correlation ID: 4d9f5a16-e2e6-4c26-a30e-40e3aa89d53b
Timestamp: 2022-08-10 13:15:35Z
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908)
at com.eclerx.email.AccessProvider.accessToken(AccessProvider.java:27)
... 1 more
Caused by: com.microsoft.aad.msal4j.MsalServiceException: AADSTS1002012: The provided value
for scope User.read openid profile offline_access Mail.Read is not valid. Client credential
flows must have a scope value with /.default suffixed to the resource identifier (application
ID URI).
Trace ID: ac0c217d-72c7-4ba0-9472-16c711ceea00
Correlation ID: 4d9f5a16-e2e6-4c26-a30e-40e3aa89d53b
Timestamp: 2022-08-10 13:15:35Z
at
com.microsoft.aad.msal4j.MsalServiceExcepti
onFactory.fromHttpResponse(MsalServiceExceptionFactory.java:45)
at
Code is as below
import java.util.Arrays;
import java.util.List;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.azure.identity.DeviceCodeCredential;
import com.azure.identity.DeviceCodeCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.models.Message;
import com.microsoft.graph.models.User;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.requests.MessageCollectionPage;
import okhttp3.Request;
public class AuthenticationBuilder2 {
private static final String CLIENT_ID = "*************";
private static final String CLIENT_SECRET_ID = "**************";
private static final String CLIENT_SECRET = "****************";
private static final String AUTH_TENANT = "*************";
private static final List<String> graphApiScopes = Arrays.asList("Mail.Read","User.read");
public AuthenticationBuilder2() {
}
public static AccessProvider initializeGraphForUserAuth() {
ClientSecretCredential clientSecretCredential =
new ClientSecretCredentialBuilder()
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.tenantId(AUTH_TENANT)
.build();
/*final DeviceCodeCredential deviceCodeCred = new DeviceCodeCredentialBuilder()
.clientId(CLIENT_ID)
.tenantId(AUTH_TENANT)
.challengeConsumer(challange -> System.out.println(challange.getMessage()))
.build();*/
TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(graphApiScopes, clientSecretCredential);
//TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(graphApiScopes, deviceCodeCred);
GraphServiceClient<Request> graphServiceClient = GraphServiceClient.builder()
.authenticationProvider(tokenCredentialAuthProvider)
.buildClient();
return new AccessProvider(graphServiceClient, tokenCredentialAuthProvider);
}
public static void main(String[] args) {
AccessProvider accessProvider = AuthenticationBuilder.initializeGraphForUserAuth();
// get token
System.out.println("token : " + accessProvider.accessToken());
System.out.println(accessProvider.getServiceClient().users());
User user = accessProvider.getServiceClient().me().buildRequest().get();
System.out.println("My UserName :: "+user.displayName);
final MessageCollectionPage messagePage = accessProvider.getServiceClient().me().messages()
.buildRequest().top(3).select("subject").get();
List<Message> messageList = messagePage.getCurrentPage();
for(Message msg : messageList) {
System.out.println("Subject -> "+msg.subject);
}
}
}
You are using the client credential flow here, which means that you cannot dynamically request scopes. You must configure your required permission scopes on your app registration in apps.dev.microsoft.com, then you set the value of scope in your code to https://graph.microsoft.com/.default.
please use string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
ref doc - https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-acquire-token?tabs=dotnet#azure-ad-v10-resources
https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#client-credentials-provider
Hope this will help , please let us know if have any question
Thanks

RestAssured with duplicate query parameters

Working on an automated test pack that uses REST-assured rest-assured-3.3.0. Have an endpoint to test that has a number of duplicate Query parameters used to drive it's search function:
/count?taskTypes=EAS&taskTypes=MBO&taskTypes=OTHER&taskAges=NEW&taskAges=EXISTING&ageSelection=ALL
REST-assured provides a params(Map<String, ?> var1) in it's request specification to pass in parameters. However this uses Map which can not contain duplicate keys. Therefore in order to construct the query in test code I am setting up the parameters and associated values in a Multimap:
import com.google.common.collect.Multimap;
Multimap<String, String> searchParams = ArrayListMultimap.create();
searchParams.put("taskTypes", "EAS");
searchParams.put("taskTypes", "MBO");
searchParams.put("taskTypes", "OTHER");
searchParams.put("taskAges", "NEW");
searchParams.put("taskAges", "EXISTING");
searchParams.put("ageSelection", "ALL");
The Multimap is then passed into a function to format the query and set up a GET request using REST-assured:
import org.apache.http.client.utils.URIBuilder;
import io.restassured.http.Method;
import io.restassured.response.ExtractableResponse;
import io.restassured.specification.RequestSpecification;
protected ExtractableResponse request(Method method, Multimap<String, ?> params, String url)
{
URIBuilder builder = new URIBuilder();
for (Map.Entry<String, ?> entry : params.entries()) {
builder.addParameter(entry.getKey(), entry.getValue().toString());
}
try {
url = url + builder.build().toString();
} catch(URISyntaxException e) {}
return getRequestSpec()
.when()
.headers("Content-Type", "application/json")
.request(method, url)
.then()
.spec(getResponseSpec(method))
.extract();
}
Running this code results in a request with the following parameters:
/count?taskTypes=OTHER&taskAges=EXISTING&ageSelection=ALL
The problem is that REST-assured appears to remove the duplicate query parameters passed to it in the url parameter. Interestingly REST-assured offers the following interface given().queryParam("param", 1,2,3)
Check that for loop here:
URIBuilder builder = new URIBuilder();
for (Map.Entry<String, ?> entry : params.entries()) {
builder.addParameter(entry.getKey(), entry.getValue().toString());
}
looks like despite using Multimap for params you are still ending with Map for the builder. Here is where you got rid of duplicated keys anyway.
queryParam(String var1, Collection<?> var2)
should work. I have used it in the past for rest-assured-3.*.
Example:
List<String> multiQuery = new ArrayList<>();
multiquery.add("EAS");
multiquery.add("MBO");
queryParam("taskTypes", multiQuery);

cloud function doesn't capture pubsub message, even though it is triggered

In my code I have 2 cloud functions, cf1 and cf2. cf1 is triggered via pubsub topic t1 with a Google Scheduler cron job every 10 minutes and creates a list and sends it to topic t2 that triggers cf2. When I use the Google's example for the cf2 I can see my message and it works. However when I deploy my own code and log the message this is what I see: ```
cf2.accept:81) - data
.accept:83) - ms {"data_":{"bytes":[],"hash":0},"messageId_":"","orderingKey_":"","memoizedIsInitialized":-1,"unknownFields":{"fields":{},"fieldsDescending":{}},"memoizedSize":-1,"memoizedHashCode":0}
My code is: ```
public class cf2 implements BackgroundFunction<PubsubMessage> {
#Override
public void accept(PubsubMessage message, Context context) throws Exception {
if (message.getData() == null) {
logger.info("No message provided");
return;
}
String messageString = new String(
Base64.getDecoder().decode(message.getData().toStringUtf8()),
StandardCharsets.UTF_8);
logger.info(messageString);
logger.info("Starting the job");
String data = message.getData().toStringUtf8();
logger.info("data "+ data);
String ms = new Gson().toJson(message);
logger.info("ms "+ ms);
}```
But when I use Google's example code :
package com.example;
import com.example.Example.PubSubMessage;
import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.Context;
import java.util.Base64;
import java.util.Map;
import java.util.logging.Logger;
public class Example implements BackgroundFunction<PubSubMessage> {
private static final Logger logger = Logger.getLogger(Example.class.getName());
#Override
public void accept(PubSubMessage message, Context context) {
String data = message.data != null
? new String(Base64.getDecoder().decode(message.data))
: "empty message";
logger.info(data);
}
public static class PubSubMessage {
String data;
Map<String, String> attributes;
String messageId;
String publishTime;
}
}
I see my message body very neatly in the logs. Can someone help me with what is wrong with my code?
Here's how I deploy my function:
gcloud --project=${PROJECT_ID} functions deploy \
cf2 \
--entry-point=path.to.cf2 \
--runtime=java11 \
--trigger-topic=t2 \
--timeout=540\
--source=folder \
--set-env-vars="PROJECT_ID=${PROJECT_ID}" \
--vpc-connector=projects/${PROJECT_ID}/locations/us-central1/connectors/appengine-default-connect
and when I log the message.getData() I get <ByteString#37c278a2 size=0 contents=""> while I know message is not empty ( I made another test subscription on the topic that helps me see the message there )
You need to define what is a PubSub message. This part is missing in your code and I don't know which PubSubMessage type you are using:
public static class PubSubMessage {
String data;
Map<String, String> attributes;
String messageId;
String publishTime;
}
It should solve your issue. Let me know.

Using M2Doc programmatically : Error in the generated .docx document

I'm trying to use M2Doc programmatically, I managed to generate my .docx file without getting errors in the validation part but I'm getting the following Error in the generated document:
{m:self.Name} Couldn't find the 'aqlFeatureAccess(org.eclipse.emf.common.util.URI.Hierarchical,java.lang.String)' service
The "self.Name" part is what I wrote in my template.
I think I'm lacking some kind of reference to a service but I don't know how to fix it.
The self variable is a reference to a model based on a meta-model I created. But I'm not sure I imported it correctly in my code.
I based my code on the code I found on the M2Doc website + some code I found on their GitHub, especially concerning how to add a service in the queryEnvironment.
I searched in the source code of acceleo and M2Doc to see which services they add but it seems that they already import all the services I'm using.
As I said, the validation part is going well and doesn't generate a validation file.
public static void parseDocument(String templateName) throws Exception{
final URI templateURI = URI.createFileURI("Template/"+templateName+"."+M2DocUtils.DOCX_EXTENSION_FILE);
final IQueryEnvironment queryEnvironment =
org.eclipse.acceleo.query.runtime.Query.newEnvironmentWithDefaultServices(null);
final Map<String, String> options = new HashMap<>(); // can be empty
M2DocUtils.prepareEnvironmentServices(queryEnvironment, templateURI, options); // delegate to IServicesConfigurator
prepareEnvironmentServicesCustom(queryEnvironment, options);
final IClassProvider classProvider = new ClassProvider(ClassLoader.getSystemClassLoader()); // use M2DocPlugin.getClassProvider() when running inside Eclipse
try (DocumentTemplate template = M2DocUtils.parse(templateURI, queryEnvironment, classProvider)) {
ValidationMessageLevel validationLevel = validateDocument(template, queryEnvironment, templateName);
if(validationLevel == ValidationMessageLevel.OK){
generateDocument(template, queryEnvironment, templateName, "Model/ComplexKaosModel.kaos");
}
}
}
public static void prepareEnvironmentServicesCustom(IQueryEnvironment queryEnvironment, Map<String, String> options){
Set<IService> services = ServiceUtils.getServices(queryEnvironment, FilterService.class);
ServiceUtils.registerServices(queryEnvironment, services);
M2DocUtils.getConfigurators().forEach((configurator) -> {
ServiceUtils.registerServices(queryEnvironment, configurator.getServices(queryEnvironment, options));
});
}
public static void generateDocument(DocumentTemplate template, IQueryEnvironment queryEnvironment,
String templateName, String modelPath)throws Exception{
final Map<String, Object> variable = new HashMap<>();
variable.put("self", URI.createFileURI(modelPath));
final Monitor monitor = new BasicMonitor.Printing(System.out);
final URI outputURI = URI.createFileURI("Generated/"+templateName+".generated."+M2DocUtils.DOCX_EXTENSION_FILE);
M2DocUtils.generate(template, queryEnvironment, variable, outputURI, monitor);
}
The variable "self" contains an URI:
variable.put("self", URI.createFileURI(modelPath));
You have to load your model and set the value of self to an element from your model using something like:
final ResourceSet rs = new ResourceSetImpl();
final Resource r = rs.getResource(uri, true);
final EObject value = r.getContents()...;
variable.put("self", value);
You can get more details on resource loading in the EMF documentation.

How to call an aws java lambda function from another AWS Java Lambda function when both are in same account, same region

I have a java aws lambda function or handler as AHandler that does some stuff e.g. It has been subscribed to SNS events, It parses that SNS event and log relevant data to the database.
I have another java aws lambda BHandler, Objective of this BHandler to receive a request from AHandler and provide a response back to AHandler. Because BHandler's objective is to provide a response with some json data. and that would be used by the AHandler.
May I see any clear example which tells how we can do such things ?
I saw this example call lambda function from a java class and Invoke lambda function from java
My question talks about that situation, when one aws java lambda function (or handler) calls to another aws java lambda function when both are in same region, same account,same vpc execution stuff, same rights. In that case aws java lambda function can directly call( or invoke) to another or still it has to provide aws key,region etc stuff (as in above links) ? A clear example/explanation would be very helpful.
EDIT
The AHandler who is calling another Lambda function (BHandler) , exist on same account have given complete AWSLambdaFullAccess with everything e.g.
“iam:PassRole",
"lambda:*",
Here is the code to call :
Note : Below code works when I call the same function with everything same from a normal java main function. But its not working like calling from on lambda function (like ALambdaHandler calling BLambdaHandler as a function call). Even its not returning any exception. Its just showing timeout, its got stuck at the code of: lambdaClient.invoke
String awsAccessKeyId = PropertyManager.getSetting("awsAccessKeyId");
String awsSecretAccessKey = PropertyManager.getSetting("awsSecretAccessKey");
String regionName = PropertyManager.getSetting("regionName");
String geoIPFunctionName = PropertyManager.getSetting("FunctionName");
Region region;
AWSCredentials credentials;
AWSLambdaClient lambdaClient;
credentials = new BasicAWSCredentials(awsAccessKeyId,
awsSecretAccessKey);
lambdaClient = (credentials == null) ? new AWSLambdaClient()
: new AWSLambdaClient(credentials);
region = Region.getRegion(Regions.fromName(regionName));
lambdaClient.setRegion(region);
String returnGeoIPDetails = null;
try {
InvokeRequest invokeRequest = new InvokeRequest();
invokeRequest.setFunctionName(FunctionName);
invokeRequest.setPayload(ipInput);
returnDetails = byteBufferToString(
lambdaClient.invoke(invokeRequest).getPayload(),
Charset.forName("UTF-8"),logger);
} catch (Exception e) {
logger.log(e.getMessage());
}
EDIT
I did everything as suggested by others and followed everything. At the end I reached to AWS support, and the problem was related to some VPC configurations stuff, and that got solved.If you have encountered similar stuff, then may be check security configs, VPC stuff.
We have achieved this by using com.amazonaws.services.lambda.model.InvokeRequest.
Here is code sample.
public class LambdaInvokerFromCode {
public void runWithoutPayload(String functionName) {
runWithPayload(functionName, null);
}
public void runWithPayload(String functionName, String payload) {
AWSLambdaAsyncClient client = new AWSLambdaAsyncClient();
client.withRegion(Regions.US_EAST_1);
InvokeRequest request = new InvokeRequest();
request.withFunctionName(functionName).withPayload(payload);
InvokeResult invoke = client.invoke(request);
System.out.println("Result invoking " + functionName + ": " + invoke);
}
public static void main(String[] args) {
String KeyName ="41159569322017486.json";
String status = "success";
String body = "{\"bucketName\":\""+DBUtils.S3BUCKET_BULKORDER+"\",\"keyName\":\""+KeyName+"\", \"status\":\""+status+"\"}";
System.out.println(body);
JSONObject inputjson = new JSONObject(body);
String bucketName = inputjson.getString("bucketName");
String keyName = inputjson.getString("keyName");
String Status = inputjson.getString("status");
String destinationKeyName = keyName+"_"+status;
LambdaInvokerFromCode obj = new LambdaInvokerFromCode();
obj.runWithPayload(DBUtils.FILE_RENAME_HANDLER_NAME,body);
}
}
Make sure the role which your Lambda function executes with has lambda:InvokeFunction permission.
Then use AWS SDK to invoke the 2rd function. (Doc: http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/lambda/AWSLambdaClient.html#invoke(com.amazonaws.services.lambda.model.InvokeRequest))
Edit: For such a scenario, consider using Step Functions.
We had similar problem and tried to gather various implementations to achieve this. Turns out it had nothing to do with the code.
Few basic rules:
Ensure proper policy and role for your lambda function, at minimum:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:::"
},
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
""
]
}
]
}
Have functions in same regions.
No VPC configurations needed. If your applications have VPC, make sure your lambda function has appropriate role policy (refer AWSLambdaVPCAccessExecutionRole)
Most important (primarily why it was failing for us), set right timeouts and heap sizes. Calling Lambda is going to wait until called one is finished. Simple math of 2x the called lambda values works. Also this was only with java lambda function calling another java lambda function. With node js lambda function calling another lambda function did not have this issue.
Following are some implementations that works for us:
Using service interface
import com.amazonaws.regions.Regions;
import com.amazonaws.services.lambda.AWSLambdaAsyncClientBuilder;
import com.amazonaws.services.lambda.invoke.LambdaInvokerFactory;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class LambdaFunctionHandler implements RequestHandler {
#Override
public String handleRequest(Object input, Context context) {
context.getLogger().log("Input: " + input);
FineGrainedService fg = LambdaInvokerFactory.builder()
.lambdaClient(
AWSLambdaAsyncClientBuilder.standard()
.withRegion(Regions.US_EAST_2)
.build()
)
.build(FineGrainedService.class);
context.getLogger().log("Response back from FG" + fg.getClass());
String fgRespone = fg.callFineGrained("Call from Gateway");
context.getLogger().log("fgRespone: " + fgRespone);
// TODO: implement your handler
return "Hello from Gateway Lambda!";
}
}
import com.amazonaws.services.lambda.invoke.LambdaFunction;
public interface FineGrainedService {
#LambdaFunction(functionName="SimpleFineGrained")
String callFineGrained(String input);
}
Using invoker
import java.nio.ByteBuffer;
import com.amazonaws.services.lambda.AWSLambdaClient;
import com.amazonaws.services.lambda.model.InvokeRequest;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class LambdaFunctionHandler implements RequestHandler {
#Override
public String handleRequest(Object input, Context context) {
context.getLogger().log("Input: " + input);
AWSLambdaClient lambdaClient = new AWSLambdaClient();
try {
InvokeRequest invokeRequest = new InvokeRequest();
invokeRequest.setFunctionName("SimpleFineGrained");
invokeRequest.setPayload("From gateway");
context.getLogger().log("Before Invoke");
ByteBuffer payload = lambdaClient.invoke(invokeRequest).getPayload();
context.getLogger().log("After Inoke");
context.getLogger().log(payload.toString());
context.getLogger().log("After Payload logger");
} catch (Exception e) {
// TODO: handle exception
}
// TODO: implement your handler
return "Hello from Lambda!";
}
}
AWSLambdaClient should be created from builder.
You can use LambdaClient to invoke Lambda asynchronously by passing InvocationType.EVENT parameter. Look at an example:
LambdaClient lambdaClient = LambdaClient.builder().build();
InvokeRequest invokeRequest = InvokeRequest.builder()
.functionName("functionName")
.invocationType(InvocationType.EVENT)
.payload(SdkBytes.fromUtf8String("payload"))
.build();
InvokeResponse response = lambdaClient.invoke(invokeRequest);

Categories