Does Microsoft.Identity.Web include a class to get and read a OIDC Discovery Document (Well Known Configuration)? - asp.net-core-webapi

OIDC has a "Well Known Configuration" page that is commonly hosted by the Identity Provider (IDP), also called a Discovery Document. I am trying to figure out Microsoft.Identity.Web and can't seem to find a way to read in the discovery document using that framework.
I currently am using the IdentityModel.Client code. It has a way to get the Discovery document.
It seems odd that Microsoft.Idetntity would not have a similar feature, but maybe Microsoft thought that everyone would fit in the "happy path" of just calling AddMicrosoftIdentityWebApi and not need the discovery document.

I use the following code to parse the discovery document:
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OpenID_Connect_client.Models
{
public class OpenIDSettings : IOpenIDSettings
{
public string EndPoint { get; }
public string Issuer { get; }
public string jwks_uri { get; }
public string authorization_endpoint { get; }
public string token_endpoint { get; }
public string userinfo_endpoint { get; }
public string end_session_endpoint { get; }
public string check_session_iframe { get; }
public string revocation_endpoint { get; }
public string introspection_endpoint { get; }
public string device_authorization_endpoint { get; }
public ICollection<string> scopes_supported { get; }
public ICollection<string> claims_supported { get; }
public ICollection<string> IdTokenSigningAlgValuesSupported { get; }
public ICollection<string> ResponseModesSupported { get; }
public ICollection<string> ResponseTypesSupported { get; }
public ICollection<string> GrantTypesSupported { get; }
/// <summary>
/// Will download and parse the token service openid-configuration document
/// Written by Tore Nestenius , https://www.tn-data.se </summary>
/// <param name="endpoint"></param>
public OpenIDSettings(string endpoint)
{
EndPoint = $"{endpoint}/.well-known/openid-configuration";
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
metadataAddress: EndPoint,
configRetriever: new OpenIdConnectConfigurationRetriever());
//If you get an exception here, then your Identity Server is not running or reachable
var document = configurationManager.GetConfigurationAsync().Result;
Issuer = document.Issuer;
jwks_uri = document.JwksUri;
authorization_endpoint = document.AuthorizationEndpoint;
token_endpoint = document.TokenEndpoint;
userinfo_endpoint = document.UserInfoEndpoint;
end_session_endpoint = document.EndSessionEndpoint;
check_session_iframe = document.CheckSessionIframe;
scopes_supported = document.ScopesSupported;
claims_supported = document.ClaimsSupported;
IdTokenSigningAlgValuesSupported = document.IdTokenSigningAlgValuesSupported;
ResponseModesSupported = document.ResponseModesSupported;
ResponseTypesSupported = document.ResponseTypesSupported;
GrantTypesSupported = document.GrantTypesSupported;
if (document.AdditionalData.ContainsKey("revocation_endpoint"))
revocation_endpoint = (string)(document.AdditionalData["revocation_endpoint"]);
if (document.AdditionalData.ContainsKey("introspection_endpoint"))
introspection_endpoint = (string)(document.AdditionalData["introspection_endpoint"]);
if (document.AdditionalData.ContainsKey("device_authorization_endpoint"))
device_authorization_endpoint = (string)(document.AdditionalData["device_authorization_endpoint"]);
}
}
}
It uses the Microsoft.IdentityModel.Protocols.OpenIdConnect package.

Related

Xamarin Forms Sqlite UpdateAsync updating database

Xamarin Forms 5
VS2019
Currently trying to update android only.
I've tried several different ways to update the database, but nothing seems to work. It seems to be updating cache, because if I select the same entry it has the changes, but even the ObservableCollection isn't being updated.
Here's the latest:
Model
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Android.Resource;
#nullable disable
namespace Photography.Handbook.Models
{
public partial class Aperture : INotifyPropertyChanged
{
public Aperture()
{
}
//public Aperture()
//{
// ShutterApertures = new HashSet<ShutterAperture>();
//}
[PrimaryKey]
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public string Notes { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public virtual ICollection<ShutterAperture> ShutterApertures { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Update Code
public async Task UpdateAsync(Aperture entity)
{
String databasePath = await DB.GetDatabaseFilePath();
SQLiteAsyncConnection db = new SQLiteAsyncConnection(databasePath);
var x = App.db.DBInstance.Query<Aperture>($"SELECT * FROM Aperture WHERE Id = '{entity.Id}'");
if(x != null)
{
x[0].Name = entity.Name;
x[0].Active = entity.Active;
x[0].Notes = entity.Notes;
var y = await db.UpdateAllAsync(x[0]);
}
}
Found the problem. I was overwriting the database each time I re-started the app.
Thanks everyone for the suggestions.

Troubleshooting model binding problem in ASP.NET Core 3.1 API

I'm trying to send an object via a POST request to my ASP.NET Core 3.1 API but I keep getting Bad Request error. As far as I can see, I do have a class that matches what I'm expecting perfectly but clearly it's not. How can I see exactly what the problem is?
The following fails:
public async Task<IActionResult> Post([FromBody] MyCustomObject input)
{
// Do something here...
}
If I use a dynamic, it works fine. So the following code works fine:
public async Task<IActionResult> Post([FromBody] dynamic input)
{
// Do something here...
}
As I said, I'm just getting a 400, Bad Request error. I've been going over MyCustomObject again and again and it looks identical to the object that I'm sending.
Here's what my custom class looks like:
public class CreateContactVm
{
[GuidEmptyNotAllowed]
public Guid AccountId { get; set; }
[Required]
public string AccountName { get; set; }
[GuidEmptyNotAllowed]
public Guid ContactGroupId { get; set; }
[IntZeroNotAllowed]
public int ContactType { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string EntityName { get; set; }
public List<AddEmailVm> Emails { get; set; } = new List<AddEmailVm>();
public List<AddPhoneNumberVm> PhoneNumbers { get; set; } = new List<AddPhoneNumberVm>();
public List<AddAddressVm> Locations { get; set; } = new List<AddAddressVm>();
}
Here, I use some custom validations such as [GuidEmptyNotAllowed] or [IntZeroNotAllowed]. I inspect the object I send via my POST call and it satisfies ALL of these requirements and yet it still fails.
How can I get more information about why my API method is throwing a 400 error?
UPDATE:
The following code allows me to convert what comes in as a dynamic to my CreateContactVm custom class but I really shouldn't have to do this at all:
CreateContactVm request = new CreateContactVm();
try
{
var element = (JsonElement)input; // input is the dynamic received
request = JsonUtils.Deserialize<CreateContactVm>(element.GetRawText());
}
catch(Exception e)
{
var error = e.Message;
}
This also proves that the issue is with model binding. Something in my custom class is not liking the JSON object it receives.

Problem querying a DynamoDB GSI Composite key using .NET Core Object Persistence Model

I want to query a GSI composite key using OPM. I followed the documentation example the link for which is as follows:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBContext.QueryScan.html
However I am facing two issues:
1). The same code as mentioned in the above example is throwing errors for me. I believe inside the context.QueryAsync(Hask Key target value, Operator.Between, RangeKey lower target value, RangeKey higher target value) is the syntax.
But I get error as follows for the last two range key target values:
Error CS1503 Argument 3: cannot convert from 'string' to 'System.Collections.Generic.IEnumerable'
2). How do I set the query to target a GSI instead of Normal Composite Keys.
For instructing the query to target GSI I googled and found the below code snippet : DynamoDBOperationConfig(). But how do I incorporate it into the final Context.QueryAsync method?
using Amazon.DynamoDBv2.DataModel;
using System;
using System.Collections.Generic;
using System.Text;
namespace EDCPA.Core.Models
{
[DynamoDBTable("EDCBUILDDATA1")]
public class AARes
{
[DynamoDBGlobalSecondaryIndexHashKey("PN-FGD-Index")]
public string ProjectName { get; set; }
[DynamoDBGlobalSecondaryIndexRangeKey("PN-FGD-Index")]
public string FileGeneratedDate { get; set; }
public string VehicleName { get; set; }
public string FileNameKey { get; set; }
[DynamoDBHashKey]
public string Id { get; set; }
[DynamoDBRangeKey]
public string createdTimeStamp { get; set; }
}
}
using Amazon.DynamoDBv2.DataModel;
using System;
using System.Collections.Generic;
using System.Text;
namespace EDCPA.Core.Models
{
[DynamoDBTable("EDCBUILDDATA1")]
public class AAReq
{
public string FileGeneratedFromDate { get; set; }
public string FileGeneratedToDate { get; set; }
public string ProjectName { get; set; }
public string VehicleName { get; set; }
[DynamoDBHashKey]
public string Id { get; set; }
[DynamoDBRangeKey]
public string createdTimeStamp { get; set; }
}
}
public async Task<IActionResult> AARequestAsync([FromBody] AAReq req)
{
try
{
headers = HeaderCollections.TryRetrieveToken(Request);
AmazonDynamoDBClient client = new AmazonDynamoDBClient(new StoredProfileAWSCredentials(),
RegionEndpoint.APSouth1);
DynamoDBContext context = new DynamoDBContext(client);
DynamoDBOperationConfig indexHashRangeOpConfig = new DynamoDBOperationConfig()
{
IndexName = "PN-FGD-Index",
ConsistentRead = false,
};
IEnumerable<AARes> fileKeys = await
context.QueryAsync<AARes>(req.ProjectName, QueryOperator.Between, req.FileGeneratedFromDate, req.FileGeneratedToDate);
Console.WriteLine("\nFindRepliesInLast15Days: Printing result.....");
foreach (AARes r in fileKeys)
Console.WriteLine("{0}\t{1}\t{2}\t{3}", r.FileNameKey, r.VehicleName);
1). What am I doing wrong inside the context.QueryAsync statement?
2). Also,How do I incorporate the DynamoDBOperationConfig indexHashRangeOpConfig into my final query statement?
Here is the solution to my problems above:
List<Dashboardreq> list =
await context.QueryAsync<Dashboardreq>(req.ProjectName, QueryOperator.Between, new string[] {
req.FileGeneratedFromDate+" " + Constants.DayBeginTime,
req.FileGeneratedToDate+" " + Constants.DayEndTime
}, indexHashRangeOpConfig).GetRemainingAsync();

asp.net web api list of list pass into json ajax

I have web api controller where I try to do something like this:
public IHttpActionResult Get()
{
var Rooms = db.Rooms.Select(r => new {
Id = r.Id,
Name = r.Name,
ChairNum = r.СhairNum,
Requests = r.Requests.ToList()
}).ToList();
return Ok(new { results = Rooms });
}
In Model I have this:
public partial class Room
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Room()
{
this.Requests = new HashSet<Request>();
}
public int Id { get; set; }
public string Name { get; set; }
public int СhairNum { get; set; }
public bool IsProjector { get; set; }
public bool IsBoard { get; set; }
public string Description { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Request> Requests { get; set; }
}
I tried to pass this data in service. But I have serialization error, when I call http://localhost:99999/api/Rest.
<ExceptionMessage>
The "ObjectContent`1" type could not serialize the response text for the content type "application / json; charset = utf-8".
</ ExceptionMessage>
What is the best way to pass the data like in model into json?
It helped to put in WebApiConfig:
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);

Using DTO's with OData & Web API

Using Web API and OData, I have a service which exposes Data Transfer Objects instead of the Entity Framework entities.
I use AutoMapper to transform the EF Entities into their DTO counter parts using ProjectTo():
public class SalesOrdersController : ODataController
{
private DbContext _DbContext;
public SalesOrdersController(DbContext context)
{
_DbContext = context;
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get(ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
}
AutoMapper (V4.2.1) is configured as follows, note the ExplicitExpansion() which prevents serialisation auto expanding navigation properties when they are not requested:
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
cfg.CreateMap<SalesOrderLine, SalesOrderLineDto>()
.ForMember(dest => dest.MasterStockRecord, opt => opt.ExplicitExpansion())
.ForMember(dest => dest.SalesOrderHeader, opt => opt.ExplicitExpansion());
ExplicitExpansion() then creates a new problem where the following request throws an error:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities
The navigation property SalesOrderLines is unknown to EF so this error is pretty much what I expected to happen. The question is, how do I handle this type of request?
The ProjectTo() method does have an overload that allows me to pass in an array of properties that require expansion, I found & modified the extension method ToNavigationPropertyArray to try and parse the request into a string array:
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray());
}
public static string[] ToNavigationPropertyArray(this ODataQueryOptions source)
{
if (source == null) { return new string[]{}; }
var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');
for (var expandIndex = 0; expandIndex < expandProperties.Length; expandIndex++)
{
// Need to transform the odata syntax for expanding properties to something EF will understand:
// OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";
// But EF wants it like this: "SalesOrderLines.MasterStockRecord";
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", "");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", ".");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", "");
}
var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');
//Now do the same for Select (incomplete)
var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();
return propertiesToExpand;
}
This works for expand, so now I can handle a request like the following:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
or a more complicated request like:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)
However, more complicated request that try to combine $select with $expand will fail:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)
Sequence contains no elements
So, the question is: am I approaching this the right way?
It feels very smelly that I would have to write something to parse and transform the ODataQueryOptions into something EF can understand.
It seems this is a rather popular topic:
odata-expand-dtos-and-entity-framework
how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand
web-api-queryable-how-to-apply-automapper
how-do-i-map-an-odata-query-against-a-dto-to-another-entity
While most of these suggest using ProjectTo, none seem to address serialisation auto expanding properties, or how to handle expansion if ExplictExpansion has been configured.
Classes and Config below:
Entity Framework (V6.1.3) entities:
public class SalesOrderHeader
{
public string SalesOrderNumber { get; set; }
public string Alpha { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}
public class SalesOrderLine
{
public string SalesOrderNumber { get; set; }
public string OrderLineNumber { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderHeader SalesOrderHeader { get; set; }
public virtual MasterStockRecord MasterStockRecord { get; set; }
}
public class MasterStockRecord
{
public string ProductCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData (V6.13.0) Data Transfer Objects:
public class SalesOrderDto
{
[Key]
public string SalesOrderNumber { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLineDto> SalesOrderLines { get; set; }
}
public class SalesOrderLineDto
{
[Key]
[ForeignKey("SalesOrderHeader")]
public string SalesOrderNumber { get; set; }
[Key]
public string OrderLineNumber { get; set; }
public string LineType { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderDto SalesOrderHeader { get; set; }
public virtual StockDto MasterStockRecord { get; set; }
}
public class StockDto
{
[Key]
public string StockCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData Config:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");
I have created an Automapper explicit navigation expansion utility function that should work with N-deph expands. Posting it here since it might help someone.
public List<string> ProcessExpands(IEnumerable<SelectItem> items, string parentNavPath="")
{
var expandedPropsList = new List<String>();
if (items == null) return expandedPropsList;
foreach (var selectItem in items)
{
if (selectItem is ExpandedNavigationSelectItem)
{
var expandItem = selectItem as ExpandedNavigationSelectItem;
var navProperty = expandItem.PathToNavigationProperty?.FirstSegment?.Identifier;
expandedPropsList.Add($"{parentNavPath}{navProperty}");
//go recursively to subproperties
var subExpandList = ProcessExpands(expandItem?.SelectAndExpand?.SelectedItems, $"{parentNavPath}{navProperty}.");
expandedPropsList = expandedPropsList.Concat(subExpandList).ToList();
}
}
return expandedPropsList;
}
You can call it with :
var navExp = ProcessExpands(options?.SelectExpand?.SelectExpandClause?.SelectedItems)
it will return a list with ["Parent" ,"Parent.Child"]
I never really managed to work this one out. The ToNavigationPropertyArray() extension method helps a little, but does not handle infinite depth navigation.
The real solution is to create Actions or Functions to allow clients to request data requiring a more complicated query.
The other alternative is to make multiple smaller/simple calls then aggregate the data on the client, but this isn't really ideal.
When you want to mark something for explicit expansion in AutoMapper, you need to also opt-back-in when calling ProjectTo<>().
// map
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
// updated controller
[EnableQuery]
public IQueryable<SalesOrderDto> Get()
{
return _dbContext.SalesOrders
.ProjectTo<SalesOrderDto>(
AutoMapperConfig.Config,
so => so.SalesOrderLines,
// ... additional opt-ins
);
}
While the AutoMapper wiki does state this, the example is perhaps a little misleading by not including the paired ExplicitExpansion() call.
To control which members are expanded during projection, set ExplicitExpansion in the configuration and then pass in the members you want to explicitly expand:

Resources