I had a question with regards to custom authorization for AWS API Gateway using a lambda coded in C#. In the documentation for AWS Lambdas, the function signature is as follows:
returnType handler-name(inputType input, ILambdaContext context) {
...
}
The inputType and returnType need to be specified for the function handler. For custom authorization in API Gateway, what should the inputType and returnTypes be? Thanks in advance.
You can opt for a strongly-typed approach without inventing custom classes that need to follow the required schema.
Use Nuget package:
Amazon.Lambda.APIGatewayEvents
Input schema:
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-input.html
Output schema:
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html
Your function prototype can then resemble:
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
public class Function
{
public APIGatewayCustomAuthorizerResponse FunctionHandler(APIGatewayCustomAuthorizerRequest input, ILambdaContext context)
{
bool ok = false;
// authorization logic here...
if(input.AuthorizationToken == "up-down-left-right-a-b-select-start")
{
ok = true;
}
return new APIGatewayCustomAuthorizerResponse
{
PrincipalID = "***",//principal info here...
UsageIdentifierKey = "***",//usage identifier here (optional)
PolicyDocument = new APIGatewayCustomAuthorizerPolicy
{
Version = "2012-10-17",
Statement = new List<APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement>() {
new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement
{
Action = new HashSet<string>(){"execute-api:Invoke"},
Effect = ok ? "Allow" : "Deny",
Resource = new HashSet<string>(){ "***" } // resource arn here
}
},
}
};
}
}
I thought I would elaborate this a bit. This uses part of what was done here as well as tried to make it like the example they give us here.
http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html
I am not sure if it needs to be async or not? I didn't and this seemed to work pretty well for a basic start.
public class Authorize
{
public Authorize() { }
public AuthPolicy AuthorizeHandler(TokenAuthorizerContext request, ILambdaContext context)
{
var token = request.AuthorizationToken;
switch (token.ToLower())
{
case "allow":
return generatePolicy("user", "Allow", request.MethodArn);
}
return null;
}
private AuthPolicy generatePolicy(string principalId, string effect, string resource)
{
AuthPolicy authResponse = new AuthPolicy();
authResponse.policyDocument = new PolicyDocument();
authResponse.policyDocument.Version = "2012-10-17";// default version
authResponse.policyDocument.Statement = new Statement[1];
Statement statementOne = new Statement();
statementOne.Action = "execute-api:Invoke"; // default action
statementOne.Effect = effect;
statementOne.Resource = resource;
authResponse.policyDocument.Statement[0] = statementOne;
return authResponse;
}
}
public class TokenAuthorizerContext
{
public string Type { get; set; }
public string AuthorizationToken { get; set; }
public string MethodArn { get; set; }
}
public class AuthPolicy
{
public PolicyDocument policyDocument { get; set; }
public string principalId { get; set; }
}
public class PolicyDocument
{
public string Version { get; set; }
public Statement[] Statement { get; set; }
}
public class Statement
{
public string Action { get; set; }
public string Effect { get; set; }
public string Resource { get; set; }
}
I wanted to post the solution that I used that worked for me. Thanks to Josh Maag for pointing me in the right direction. Basically, I created a few simple classes:
public class TokenAuthorizerContext
{
public string Type { get; set; }
public string AuthorizationToken { get; set; }
public string MethodArn { get; set; }
}
public class AuthPolicy
{
public PolicyDocument policyDocument { get; set; }
public string principalId { get; set; }
}
public class PolicyDocument
{
public string Version { get; set; }
public Statement[] Statement { get; set; }
}
public class Statement
{
public string Action { get; set; }
public string Effect { get; set; }
public string Resource { get; set; }
}
```
With the above classes created, the signature to my handler is:
public async Task<AuthPolicy> FunctionHandler(TokenAuthorizerContext request, ILambdaContext context)
You should really take a look at the following link and try to follow it through. The full tutorial is written using Python, so if you're unfamiliar with it, just do your best to follow along and read the full walk-through, but this link will explain the C# portion:
http://docs.aws.amazon.com/lambda/latest/dg/get-started-step5-optional.html
Essentially, the string:
returnType handler-name(inputType input, ILambdaContext context) {
Would be something like this (copied from the AWS page):
public string MyHandler(int count, ILambdaContext context) { ... }
public is added as a scope modifier, the returnType the developer has chosen is string the handler-name is MyHandler and the inputType is int
Related
I have a small database that has been created by EF using a typical model class:
public class Metrics
{
public int Id { get; set; }
public string? MetricValue { get; set; }
public string? MetricHost { get; set; }
public string? MetricTime { get; set; }
}
The database is now populated with data and my Minimal API can return all the entries from:
app.MapGet("/metric", async (DataContext context) => await context.Metrics.ToListAsync());
And also, I can query by Id:
app.MapGet("/metric/{id}", async (DataContext context, int id) =>
await context.Metrics.FindAsync(id) is Metric metric ?
Results.Ok(metric) :
Results.NotFound("Metric not found"));
I've been searching the web for something that would show how to search by another property but have not found anything that works. e.g.,
app.MapGet("/hostnames/{MetricHost}"...
This article on CodeMaze is the closest I've found but none of the examples seem to work:
https://code-maze.com/aspnetcore-query-string-parameters-minimal-apis/
Any help is appreciated. Here's an example that did not work:
app.MapGet("/search", (SearchCriteria criteria) =>
{
return $"Host: {criteria.MetricHost}, Id: {criteria.Id}";
});
With model changes:
public class Metric
{
public int Id { get; set; }
public string? MetricValue { get; set; }
public string? MetricHost { get; set; }
public string? MetricTime { get; set; }
public static ValueTask<Metric?> BindAsync(HttpContext context, ParameterInfo parameter)
{
string hostname = context.Request.Query["MetricHost"];
int.TryParse(context.Request.Query["Id"], out var id);
var result = new Metric
{
MetricHost = hostname,
Id = id
};
return ValueTask.FromResult<Metric?>(result);
}
}
You are binding wrong type, BindAsync should be part of SearchCriteria:
app.MapGet("/search", (SearchCriteria criteria, DataContext context) =>
{
IQueryable<Metric> query = context.Metrics;
if(criteria.MetricHost is not null)
{
query = query.Where(m => m.MetricHost == criteria.MetricHost)
}
// ... rest of filters
return await query.ToListAsync();
});
public class SearchCriteria
{
public string? MetricHost { get; set; }
// ... rest of filters
public static ValueTask<SearchCriteria?> BindAsync(HttpContext context, ParameterInfo parameter)
{
string hostname = context.Request.Query["MetricHost"];
// ... rest of filters
var result = new SearchCriteria
{
MetricHost = hostname,
};
return ValueTask.FromResult<SearchCriteria?>(result);
}
}
Read more:
Filtering in EF Core
Im currently trying to implement CRUD functionality with a dbfactory and generics with microsoft EF, but while listing entries is working, making changes to the db is currently not working.
public class AbstractDataModel
{
[Key]
public Guid gid { get; set; }
}
Model
class SalesOrder : AbstractDataModel
{
public int salesOrderID { get; set; }
public int productID { get; set; }
public int customerID { get; set; }
public Guid createdBy { get; set; }
public string dateCreated { get; set; }
public string orderDate { get; set; }
public string orderStatus { get; set; }
public string dateModified { get; set; }
}
A DBCore with some other functionality besides the ones listed here, which are not relevant for the factory
public class DBCore : DbContext
{
public static string connectionString = "myConnectionStringToDb";
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString);
}
Data Service which calls factory
class SalesOrderService : DBCore
{
public DbSet<SalesOrder> SalesOrders { get; set; }
public OkObjectResult GetAllSalesOrders()
{
DBFactory factory = new DBFactory();
return new OkObjectResult(JsonConvert.SerializeObject(factory.GetAll(SalesOrders)));
}
public OkObjectResult AddSalesOrder(SalesOrder order)
{
order.gid = Guid.NewGuid();
return DBFactory.AddOne(order);
}
public OkObjectResult UpdateSalesOrder(SalesOrder order)
{
return DBFactory.UpdateOne(order);
}
public OkObjectResult DeleteSalesOrder(SalesOrder order)
{
return DBFactory.DeleteOne(order);
}
}
simple CRUD-Factory,
class DBFactory : DBCore
{
public DbSet<UserModel> Users { get; set; }
public DbSet<SalesOrder> SalesOrders { get; set; }
public List<T> GetAll<T>(DbSet<T> dbset) where T : class
{
using (this)
{
return dbset.ToList();
}
}
public static OkObjectResult AddOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Add(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully added");
}
}
public static OkObjectResult UpdateOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Update(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully updated");
}
}
public static OkObjectResult DeleteOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Attach(data);
factory.Remove(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully removed");
}
}
}
Edit: Following the advices i changed the code so it should SaveChanges for the Factory, which also contains the context as a property. But it still doesnt seem to work for all database operations except listing all entries
Editv2: Thanks for the adivces it seems i have solved that problem, but a new one appeared :D
I can now do database operations like deleting entries, but now i cant list the entries anymore because the following error occurs, although the code there didnt really change:
"Executed 'GetAllOrders' (Failed, Id=5fb95793-572a-4545-ac15-76dffaa7a0cf, Duration=74ms)
[2020-10-23T14:33:43.711] System.Private.CoreLib: Exception while executing function: GetAllOrders. Newtonsoft.Json: Self referencing loop detected for property 'Context' with type 'FicoTestApp.Models.SalesOrder'. Path '[0].ChangeTracker'."
try adding
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
to your
startup.cs
it should to the job
YAML pipeline looks like below
parameters:
- name: parameter1
type: string
steps:
task: PowerShell#2
inputs:
targetType: 'inline'
script: |
# Write your PowerShell commands here.
Write-Host ${{ parameters.parameter1 }}
C# code to queue build is like
var build = new Build()
{
Definition = definition,
Project = project
};
var dict = new Dictionary<string, string> { { "parameter1", "parametervalue" } };
build.Parameters = JsonSerializer.Serialize(dict);
buildClient.QueueBuildAsync(build).Wait();
I get exception Could not queue the build because there were validation errors or warnings. A value for the 'parameter1' parameter must be provided.
Any idea to fix this issue would be helpful.
It looks that this is not possible at the moment to run pipeline with passing runtime parameters over C# SDK. You found workaround using REST API.
This is not an issue with SDK. They are developed in their own pace. So functionality which is available in REST API is not always available at the same moment in SDK. It could be available in the future, but for the moment if you want to run it programmatically you need to use REST API.
I managed to reverse engineer some client code which might help others. It may not be fully production-ready, but feel free to improve on it:
public class PipelineHttpClient : VssHttpClientBase
{
public PipelineHttpClient(Uri baseUrl, VssCredentials credentials)
: base(baseUrl, credentials)
{
}
public PipelineHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings)
: base(baseUrl, credentials, settings)
{
}
public PipelineHttpClient(Uri baseUrl, VssCredentials credentials, params DelegatingHandler[] handlers)
: base(baseUrl, credentials, handlers)
{
}
public PipelineHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings, params DelegatingHandler[] handlers)
: base(baseUrl, credentials, settings, handlers)
{
}
public PipelineHttpClient(Uri baseUrl, HttpMessageHandler pipeline, bool disposeHandler)
: base(baseUrl, pipeline, disposeHandler)
{
}
public async Task<PipelineRun> RunPipelineAsync(Guid project, int pipelineId, object parameters, string refName = "refs/heads/master")
{
var method = HttpMethod.Post;
var locationId = Guid.Parse("7859261e-d2e9-4a68-b820-a5d84cc5bb3d");
object routeValues = new
{
project,
pipelineId,
//pipelineVersion = ""
};
var version = new ApiResourceVersion("6.0");
// ensure that the refName is prefixed correctly.
refName = refName.StartsWith("refs/heads/", StringComparison.InvariantCultureIgnoreCase)
? refName
: $"refs/heads/{refName}";
var content = (HttpContent) new ObjectContent<object>(new
{
StagesToSkip = new object[0],
TemplateParameters = parameters,
Variables = new object(),
Resources = new
{
Repositories = new
{
Self = new
{
RefName = refName
}
}
}
},
new VssJsonMediaTypeFormatter(true));
var queryParameters = new Dictionary<string, string>();
return await SendAsync<PipelineRun>(method, locationId, routeValues, version, content, queryParameters, cancellationToken: CancellationToken.None);
}
}
public class Pipeline
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("revision")]
public int Revision { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("folder")]
public string Folder { get; set; }
}
public class Repository
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}
public class Self
{
[JsonProperty("repository")]
public Repository Repository { get; set; }
[JsonProperty("refName")]
public string RefName { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
}
public class Repositories
{
[JsonProperty("self")]
public Self Self { get; set; }
}
public class Resources
{
[JsonProperty("repositories")]
public Repositories Repositories { get; set; }
}
public class PipelineRun
{
[JsonProperty("pipeline")]
public Pipeline Pipeline { get; set; }
[JsonProperty("state")]
public string State { get; set; }
[JsonProperty("createdDate")]
public DateTime CreatedDate { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("resources")]
public Resources Resources { get; set; }
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
Usage:
PipelineHttpClient client = new PipelineHttpClient(...);
var project = Guid.Parse("TODO");
var pipelineId = 1234;
// pipeline parameters:
var parameters = new {meaningOfLife = 42};
var result = await client.RunPipelineAsync(project, pipelineId, parameters, "refs/heads/feature/my-branch");
if (result.State == "inProgress")
{
// TODO the pipeline is running!
}
these using statements are required for the classes above:
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
[HttpGet("/api/notes/suggested")]
public JsonResult GetSuggestedNotes(string searchText)
{
//TODO: Podpowiedzi przy wpisywaniu tytułu
JsonResult result = null;
try {
List<Note> n = db.Notes.Include(x => x.NoteTags).ToList();
result = Json(n);
}
catch(Exception e)
{
Console.WriteLine(e);
}
return result;
}
public class Note
{
public Note()
{
CreationDate = DateTime.Now;
NoteTags = new HashSet<NoteTag>();
Parts = new HashSet<Part>();
}
public int ID { get; set; }
public virtual ICollection<NoteTag> NoteTags { get; set; }
public virtual ICollection<Part> Parts { get; set; }
public DateTime? CreationDate { get; set; }
[NotMapped]
public string TagsToAdd { get; set; }
[NotMapped]
public string TagsAsSingleString {
get
{
string result = "";
foreach(var nt in NoteTags)
{
result += nt.Tag.Name + " ";
}
return result;
}
}
}
public class NoteTag
{
public int NoteId { get; set; }
public virtual Note Note { get; set; }
public int TagId { get; set; }
public virtual Tag Tag { get; set; }
}
When I try to get data using this WebAPI controller, I get 502 bad gateway. No errors, everything's fine while debugging server. Data get from database correctly.
I suspect that it could be something similar to "infinite loop" but how to prevent it? (Note class is connected to collection of NoteTag objects that are connected back to Note which probably makes this loop).
And why there are no errors if something went wrong? :/
I don't know if it still relevant but i had the same problem and what worked for me it to Configure Newtonsoft.Json
SerializerSettings.ReferenceLoopHandling = ewtonsoft.Json.ReferenceLoopHandling.Ignore.
If you are using VS2015 MVC you can add the following code:
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
in the ConfigureServices method in the Startup class.
I think the problem its recursion, can you try with an Anonymous type
NoteTags has Note , imagine if the Note->NoteTags->Note->NoteTags->Note->NoteTags ...
`List n = db.Notes.Include(x => x.NoteTags).ToList();
var e = n.select(x=> new {property=value});
result = Json(e);`
For example I have the following class:
public class PublishFacebookOpenGraphActionRequest
{
public FacebookOpenGraphActions Action { get; private set; }
public FacebookOpenGraphObjects ObjectType { get; private set; }
public Uri Url { get; private set; }
public Uri Image { get; private set; }
}
And I would like to be able to generate something like this from the current set of properties:
public class PublishFacebookOpenGraphAction
{
public FacebookOpenGraphActions Action { get; private set; }
public FacebookOpenGraphObjects ObjectType { get; private set; }
public Uri Url { get; private set; }
public Uri Image { get; private set; }
public PublishFacebookOpenGraphAction WithAction(FacebookOpenGraphActions action)
{
this.Action = action;
return this;
}
public PublishFacebookOpenGraphAction WithObjectType(FacebookOpenGraphObjects objectType)
{
this.ObjectType = objectType;
return this;
}
public PublishFacebookOpenGraphAction WithUrl(Uri url)
{
this.Url = url;
return this;
}
public PublishFacebookOpenGraphAction WithImage(Uri image)
{
this.Image = image;
return this;
}
}
I do not have a conversion from all members of PublishFacebookOpenGraphActionRequest to a fluent interface.
But maybe an half-automated generation of a fluent property-WithMember-pair is also a small step forward. This is possible with a ReSharper Live Template.
I have an example:
You can create the following code with that Live Template:
public FacebookOpenGraphActions Action { get; private set; }
public PublishFacebookOpenGraphAction WithAction(FacebookOpenGraphActions action)
{
this.Action = action;
return this;
}
by just typing fluentProp + ENTER and entering property name and its type.
Another way is to use Visual Studio's Quick Replace (Ctrl+H) by using Regular expressions.
See screenshot:
Find what: is set to: :b*public:b+{:i}:b+{:i}:b+\{:b*get;:b*private:b*set;:b*\}:b*$.
Replace with: is set to: public \1 \2 { get; private set; }\n public _class_ With\2(\1 p\2)\n\{\nthis\.\2 = p\2;\nreturn this;\n\}\n.
This adds a *With** member for all properties in current document. Unfortunatly I cannot find a way to set return type of *With** member. So I just set it to _class_. So you need to replace this _class_ by class name (manually or by another Quick Replace run).