Lambda Authorizer of type Request not working in Kotlin - amazon-web-services

I've been trying to use a custom authorizer ( lambda authorizer ) of type Request for a certain HTTP API in Kotlin.
Taking inspiration from this, I wrote the following :
class AuthorizerHandler : RequestHandler<Map<String, Any>, AuthorizerResponse>{
override fun handleRequest(input: Map<String, Any>, context: Context?): AuthorizerResponse {
val LOG = LogManager.getLogger(AuthorizerHandler::class.java)
LOG.info(input)
val headers = input["headers"]!! as Map<*, *>
val authorization = headers["authorization"]!! as String
val arn = input["routeArn"]!! as String
val tokenBody: TokenBody = TokenBody.fromToken(authorization)
LOG.info("SUB : ${tokenBody.userID}")
val proxyContext = input["requestContext"] as Map<*, *>
val principalId = proxyContext["accountId"] as String
val statement = IamPolicyResponse.Statement.builder().withResource(listOf("*"))
.withEffect("Allow")
.withAction("execute-api:Invoke")
.build()
val policyDocument = IamPolicyResponse.PolicyDocument.builder()
.withStatement(listOf(statement))
.withVersion("2012-10-17")
.build()
return AuthorizerResponse(principalId = tokenBody.userID, policyDocument = policyDocument)
}
}
TokenBody is custom class with init function fromToken that helps me deconstruct the token. It's working as expected and tokenBody.userID gives me the user's sub.
AuthorizerResponse is also a custom class which looks like this :
class AuthorizerResponse (
#JsonProperty("principalId")
val principalId: String? = null,
#JsonProperty("policyDocument")
val policyDocument: IamPolicyResponse.PolicyDocument? = null,
#JsonProperty("context")
val context: Map<String, String>? = null,
)
First I was supplying arn as the Resource when building my statement. Calling the API resulted in
{
"message": "Internal Server Error"
}
Changing Resource to * doesn't help either and throws the same 500 Internal Server Error.
Similarly, I've tried accountID as principalID as well but that doesn't work either.
I've checked the logs of the Authorizer lambda but that doesn't throw any error so it's technically working as expected after getting invoked properly.
Any ideas on how this can be fixed?
Edit : Mentioned the type of API.

Related

Kotlin & DynamoDB: Unable to resolve host "dynamodb.us-east-1.amazonaws.com": No address associated with hostname

I'm trying to connect my Kotlin Android app to DynamoDB in AWS using Android Studio. My credential details are replaced in the code below and it is connecting to an AWS sandbox. The created database client is below:
val staticCredentials = StaticCredentialsProvider {
accessKeyId = MY_ACCESS_KEY_HERE
secretAccessKey = MY_SECRET_ACCESS_KEY
}
ddb = DynamoDbClient{
region = MY_REGION_HERE
credentialsProvider = staticCredentials
}
Then I attempted to create the table using this client following these steps :
val tableNameVal = "Users"
val attDef = AttributeDefinition {
attributeName = key
attributeType = ScalarAttributeType.S
}
val keySchemaVal = KeySchemaElement {
attributeName = key
keyType = KeyType.Hash
}
val provisionedVal = ProvisionedThroughput {
readCapacityUnits = 10
writeCapacityUnits = 10
}
val request = CreateTableRequest {
attributeDefinitions = listOf(attDef)
keySchema = listOf(keySchemaVal)
provisionedThroughput = provisionedVal
tableName = tableNameVal
}
var tableArn: String
val response = ddb.createTable(request)
ddb.waitUntilTableExists { // suspend call
tableName = tableNameVal
}
tableArn = response.tableDescription!!.tableArn.toString()
println("Table $tableArn is ready")
However when I do this, I get an error when the createTable method is called:
java.net.UnknownHostException: Unable to resolve host "dynamodb.us-east-1.amazonaws.com": No address associated with hostname
I've tried doing a scan instead but I also get the same error. I thought that with AWS, if you provide the correct credentials and region, it wouldn't need any extra endpoints yet I'm still getting this "no hostname" error. I've looked at other code in JS with similar issues but couldn't get it working. Any help would be appreciated.

Unable to pass dynamic values to config records

I have a requirement to pass a JWT client assertion to the oauth2 client credentials grant config record. I'm passing the parameter as the optional parameter. But this parameter has to be generated each time the token endpoint is called for an access token. Therefore I did something like the following.
http:OAuth2ClientCredentialsGrantConfig oauth2Config = {
tokenUrl: "https://*****/oauth2/token",
clientId: "*******",
optionalParams: getJWT(),
clientSecret: "*****",
credentialBearer: oauth2:POST_BODY_BEARER
};
Here, the getJWT() method returns a map with the JWT.
function getJWT() returns map<string> {
string jwt = // logic to generate the JWT
map<string> jwtAssertion = {
"client_assertion" : jwt
};
return jwtAssertion;
}
This works only once. When the access token returned by the token endpoint expires and when the token endpoint is called again for the access token, the getJWT() method does not get called. Therefore, I suppose the new request is going with the old JWT, hence the request fails.
Is there a way to pass a dynamically changing value as a parameter to the http:OAuth2ClientCredentialsGrantConfig record?
You can achieve this by writing a custom ClientOAuth2Handler and using it as described in the imperative approach section.
Your custom handler should check for the exp value of client_assertion and create a new http:ClientOAuth2Handler with a new client_assertion when it expires. You can get an idea from the below code.
import ballerina/http;
import ballerina/oauth2;
import ballerina/jwt;
import ballerina/time;
client class CustomClientOAuth2Handler {
private http:ClientOAuth2Handler? oauthHandler = ();
private string? jwt = ();
public function init() returns error? {
self.jwt = self.getJWT();
self.oauthHandler = check self.getOauth2Handler();
}
remote function enrich(http:Request request) returns http:Request|error {
boolean isJwtExpired = check self.isJwtExpired();
if isJwtExpired {
self.jwt = self.getJWT();
self.oauthHandler = check self.getOauth2Handler();
}
http:ClientOAuth2Handler oauthHandler = check self.oauthHandler.ensureType();
return oauthHandler->enrich(request);
}
private function getJWT() returns string {
return ""; // your jwt logic
}
private function getOauth2Handler() returns http:ClientOAuth2Handler|error {
string jwt = check self.jwt.ensureType();
return new ({
tokenUrl: "https://localhost:9445/oauth2/token",
clientId: "FlfJYKBD2c925h4lkycqNZlC2l4a",
clientSecret: "PJz0UhTJMrHOo68QQNpvnqAY_3Aa",
credentialBearer: oauth2:POST_BODY_BEARER,
optionalParams: {client_assertion: jwt}
});
}
private function isJwtExpired() returns boolean|error {
// your logic to check jwt assertion expirty
string jwt = check self.jwt.ensureType();
[jwt:Header, jwt:Payload] [_, payload] = check jwt:decode(jwt);
int? assertionExpiryTime = payload.exp;
[int, decimal] currentTime = time:utcNow();
return assertionExpiryTime !is () && assertionExpiryTime <= currentTime[0];
}
}

How to attach a list of strings in the body of a web service request using katalon tool and groovy scripting

** I am facing issue in passing list of Strings and attach them in the body of a delete request in katalon using groovy scripts.***
def request = ((findTestObject('API/phone_numbers/phone_numbers.delete_from_database',[('base_url') : base_url,('account_id') : account_id,
('auth_token') : GlobalVariable.auth_token, ('credentials') : GlobalVariable.credentials])) as RequestObject)
List<String> list_Of_Numbers= {}
for (i in numbers)
{
list_Of_Numbers[i] = i
}
String body = """{"data":{"name":"'$list_Of_Numbers'"}}"""
request.setBodyContent(new HttpTextBodyContent(body))
def response = WS.sendRequest(request)

Why is captured azure eventhub information for SystemProperties and Properites empty?

I am using Azure EventHub and capturing the contents to blob storage using https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-capture-overview.
Now for the generated Avro files, why is the information stored in the properties and System properties fields empty?
NOTE Azure is populating these fields
I publish the data using a POST request with my payload as the body and authorization headers set.
Am I missing additional headers which would be required to make Azure fill these columns?
edit
so the event hub client`s POST method looks like this:
private val SB_URL = "https://$namespace.servicebus.windows.net/$eventHub"
private val HEADER_AUTHORIZATION = AzureUtil.getSasToken(SB_URL, sas_key_name, sas_key)
private val HEADER_CONTENT_TYPE = "application/atom+xml;type=entry;charset=utf-8"
private val REQ_URL = "https://$namespace.servicebus.windows.net/$eventHub/messages"
private val REQ_TIMEOUT = "60"
private val REQ_API_VERSION = "2014-01"
private val client = OkHttpClient()
private val JSON = "application/json; charset=utf-8".toMediaType()
private var callback: Callback? = null
fun registerCallback(cb: Callback) {
callback = cb
}
fun send(message: String) {
val request = Request.Builder()
.url("$REQ_URL?timeout=$REQ_TIMEOUT&api-version=$REQ_API_VERSION")
.addHeader("Content-Type", HEADER_CONTENT_TYPE)
.addHeader("Authorization", HEADER_AUTHORIZATION)
.post(message.toRequestBody(JSON))
.build()
val call = client.newCall(request)
try {
val response = call.execute()
callback!!.onResponse(call, response)
} catch (error: IOException) {
callback!!.onFailure(call, error)
}
}

Connect AWS mobile backend to DynamoDB

I am trying to use AWS mobile backend (using lambda function) to insert into dynamoDB (also configured at the mobile backend) but with no success so far.
The relevant code:
'use strict';
console.log("Loading function");
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region:process.env.MOBILE_HUB_PROJECT_REGION});
exports.handler = function(event, context, callback) {
var responseCode = 200;
var requestBody, pathParams, queryStringParams, headerParams, stage,
stageVariables, cognitoIdentityId, httpMethod, sourceIp, userAgent,
requestId, resourcePath;
console.log("request: " + JSON.stringify(event));
// Request Body
requestBody = event.body;
if (requestBody !== undefined && requestBody !== null) {
// Set 'test-status' field in the request to test sending a specific response status code (e.g., 503)
responseCode = JSON.parse(requestBody)['test-status'];
}
// Path Parameters
pathParams = event.path;
// Query String Parameters
queryStringParams = event.queryStringParameters;
// Header Parameters
headerParams = event.headers;
if (event.requestContext !== null && event.requestContext !== undefined) {
var requestContext = event.requestContext;
// API Gateway Stage
stage = requestContext.stage;
// Unique Request ID
requestId = requestContext.requestId;
// Resource Path
resourcePath = requestContext.resourcePath;
var identity = requestContext.identity;
// Amazon Cognito User Identity
cognitoIdentityId = identity.cognitoIdentityId;
// Source IP
sourceIp = identity.sourceIp;
// User-Agent
userAgent = identity.userAgent;
}
// API Gateway Stage Variables
stageVariables = event.stageVariables;
// HTTP Method (e.g., POST, GET, HEAD)
httpMethod = event.httpMethod;
// TODO: Put your application logic here...
let params = {
Item:{
"prop1":0,
"prop2":"text"
},
TableName:"testTable"
};
docClient.put(params, function(data, err){
if(err)
responseCode = 500;
else
{
responseCode = 200;
context.succeed(data);
}
});
// For demonstration purposes, we'll just echo these values back to the client
var responseBody = {
requestBody : requestBody,
pathParams : pathParams,
queryStringParams : queryStringParams,
headerParams : headerParams,
stage : stage,
stageVariables : stageVariables,
cognitoIdentityId : cognitoIdentityId,
httpMethod : httpMethod,
sourceIp : sourceIp,
userAgent : userAgent,
requestId : requestId,
resourcePath : resourcePath
};
var response = {
statusCode: responseCode,
headers: {
"x-custom-header" : "custom header value"
},
body: JSON.stringify(responseBody)
};
console.log("response: " + JSON.stringify(response))
context.succeed(response);
};
this doesn't put the item to the table for some reason.
I gave the necessary permissions using the roles part, anything I am missing?
**responseCode is only for testing purposes.
Edit:
tried AWS node.js lambda request dynamodb but no response (no err, no return data) and doesn't work either.
Edit2:
Added the full handler code. (it the default generated code when creating first AWS lambda).
I have refactored some bits of your code to look much simpler and use async/await (make sure to select Node 8.10 as the running environment for your function) instead of callbacks. I also got rid of the context and callback parameters, as they were used for older versions of NodeJS. Once you're using Node 8+, async/await should be the default option.
Also, it is possible to chain a .promise() on docClient.putItem, so you can easily await on it, making your code way simpler. I have left only the DynamoDB part (which is what is relevant to your question)
'use strict';
console.log("Loading function");
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region:process.env.MOBILE_HUB_PROJECT_REGION});
exports.handler = async (event) => {
let params = {
Item:{
"prop0":1,
"prop2":"text"
},
TableName:"testTable"
};
try {
await docClient.put(params).promise();
} catch (e) {
console.log(e)
return {
messsage: e.message
}
}
return { message: 'Data inserted successfully' };
};
Things to keep in mind if still it does not work:
Make sure your Lambda function has the right permissions to insert items on DynamoDB (AmazonDynamoDBFullAccess will do it)
You ALWAYS have to provide the partition key when inserting items to DynamoDB. On your example, the JSON only has two properties: prop1 and prop2. If none of them are the partition key, your code will certainly fail.
Make sure you table also exists
If you code fails, just check CloudWatch logs as any exception is now captured and printed out on the console.
The reason why no data is written in the table is because the call to DynamoDB put is asynchronous and will return by calling your callback. But during that time, the rest of the code continues to execute and your function eventually finish before the call to DynamoDB has a chance to complete.
You can use the await / async keywords to make your code sychronous :
async function writeToDynamoDB(params) {
return new Promise((resolve,reject) => {
docClient.put(params, function(data, err){
if(err)
reject(500);
else
resolve(data);
});
});
}
let params = ...
var data = await writeToDynamoDB(params)
You can find sample code I wrote (in Typescript) at https://github.com/sebsto/maxi80-alexa/blob/master/lambda/src/DDBController.ts