diff --git a/.gitignore b/.gitignore index dfcfd56..00aedca 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ bld/ # Visual Studio 2015/2017 cache/options directory .vs/ +# Visual Studio Code options directory +.vscode/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Extensions/CieExtensions.cs b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Extensions/CieExtensions.cs index 4e599cf..a85bb18 100644 --- a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Extensions/CieExtensions.cs +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Extensions/CieExtensions.cs @@ -10,6 +10,7 @@ using System; using System.Security.Claims; using Microsoft.AspNetCore.Builder; +using CIE.AspNetCore.Authentication.Models.ServiceProviders; namespace CIE.AspNetCore.Authentication.Extensions { @@ -21,7 +22,26 @@ public static class CieExtensions /// /// public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, IConfiguration configuration) - => builder.AddCie(CieDefaults.AuthenticationScheme, configuration, _ => { }); + => builder.AddCie(CieDefaults.AuthenticationScheme, o => { o.LoadFromConfiguration(configuration); }); + + /// + /// Registers the using the default authentication scheme, display name, and the given options configuration. + /// + /// + /// A delegate that configures the . + /// + public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddCie(CieDefaults.AuthenticationScheme, configureOptions); + + /// + /// Registers the using the given authentication scheme, default display name, and the given options configuration. + /// + /// + /// + /// A delegate that configures the . + /// + public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + => builder.AddCie(authenticationScheme, CieDefaults.DisplayName, configureOptions); /// /// Registers the using the default authentication scheme, display name, and the given options configuration. @@ -29,9 +49,10 @@ public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, I /// /// A delegate that configures the . /// + /* public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, IConfiguration configuration, Action configureOptions) => builder.AddCie(CieDefaults.AuthenticationScheme, configuration, configureOptions); - + */ /// /// Registers the using the given authentication scheme, default display name, and the given options configuration. /// @@ -39,8 +60,10 @@ public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, I /// /// A delegate that configures the . /// + /* public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, string authenticationScheme, IConfiguration configuration, Action configureOptions) => builder.AddCie(authenticationScheme, CieDefaults.DisplayName, configuration, configureOptions); + */ /// /// Registers the using the given authentication scheme, display name, and options configuration. @@ -50,6 +73,31 @@ public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, s /// /// A delegate that configures the . /// + public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, CiePostConfigureOptions>()); + builder.Services.TryAdd(ServiceDescriptor.Singleton()); + builder.Services.AddHttpClient("cie"); + builder.Services.TryAddScoped(factory => + { + var actionContext = factory.GetService().ActionContext; + var urlHelperFactory = factory.GetService(); + return urlHelperFactory.GetUrlHelper(actionContext); + }); + builder.Services.AddOptions().Configure(configureOptions); + builder.Services.TryAddScoped(); + return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions); + } + + /// + /// Registers the using the given authentication scheme, display name, and options configuration. + /// + /// + /// + /// + /// A delegate that configures the . + /// + /* public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, IConfiguration configuration, Action configureOptions) { builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, CiePostConfigureOptions>()); @@ -64,8 +112,16 @@ public static AuthenticationBuilder AddCie(this AuthenticationBuilder builder, s builder.Services.AddOptions().Configure(o => OptionsHelper.LoadFromConfiguration(o, configuration)); return builder.AddRemoteScheme(authenticationScheme, displayName, configureOptions); } + */ + + public static AuthenticationBuilder AddServiceProvidersFactory(this AuthenticationBuilder builder) + where T : class, IServiceProvidersFactory + { + builder.Services.AddScoped(); + return builder; + } - public static IApplicationBuilder AddSpidSPMetadataEndpoints(this IApplicationBuilder builder) + public static IApplicationBuilder AddCieSPMetadataEndpoints(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Models/CieOptions.cs b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Models/CieOptions.cs index dc81317..448bfcd 100644 --- a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Models/CieOptions.cs +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Models/CieOptions.cs @@ -20,7 +20,7 @@ public CieOptions() // In AAD it sends the cleanup message to a random Reply Url and there's no deterministic way to configure it. // If you manage to get it configured, then you can set RemoteSignOutPath accordingly. RemoteSignOutPath = "/signout-cie"; - ServiceProvidersMetadataEndpointsBasePath = "/metadata-spid"; + ServiceProvidersMetadataEndpointsBasePath = "/metadata-cie"; Events = new CieEvents(); } diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Models/ServiceProviders/ContactPerson.cs b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Models/ServiceProviders/ContactPerson.cs index 5da6ee4..9a65e25 100644 --- a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Models/ServiceProviders/ContactPerson.cs +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Models/ServiceProviders/ContactPerson.cs @@ -16,13 +16,13 @@ public interface IContactPerson ContactKind GetContactKind(); ContactTypeType ContactType { get; set; } (bool, string) Validate(); - Saml.SP.ContactType GetContactForXml(ServiceProvider sp); } public abstract class BaseContactPerson : IContactPerson { private string _province; + private string[] _nace2codes; public string Municipality { get; set; } public string Province { get { return Country != "IT" ? "EE" : _province; } set { _province = value; } } @@ -31,9 +31,6 @@ public abstract class BaseContactPerson : IContactPerson public string[] EmailAddress { get; set; } public string[] TelephoneNumber { get; set; } public ContactTypeType ContactType { get; set; } - - private string[] _nace2codes; - public string VATNumber { get; set; } public string FiscalCode { get; set; } public string[] NACE2Codes { get { return _nace2codes; } set { _nace2codes = value; } } @@ -46,22 +43,37 @@ public bool IsItalian() public Saml.SP.ContactType GetContactForXml(ServiceProvider sp) { - var elements = GetSpecificElements(); - elements.Add(XmlHelpers.GetXmlElement(Saml.SamlConst.cie, Saml.SamlConst.cieNamespace, GetContactKind().ToString())); + //the code order is strange because spid-sp-test require to respect items order + var elements = new List(); + var values = new List(); + elements.Add(GetContactKind() == ContactKind.Private ? ItemsChoiceType7.Private : ItemsChoiceType7.Public); + values.Add(""); //Private and Public have no value + var (specElements, specValues) = GetSpecificElements(); + elements.AddRange(specElements); + values.AddRange(specValues); if (!string.IsNullOrWhiteSpace(VATNumber)) - elements.Add(XmlHelpers.GetXmlElement(Saml.SamlConst.cie, Saml.SamlConst.cieNamespace, ItemsChoiceType7.VATNumber.ToString(), VATNumber)); + { + elements.Add(ItemsChoiceType7.VATNumber); + values.Add(this.VATNumber); + } if (!string.IsNullOrWhiteSpace(FiscalCode)) - elements.Add(XmlHelpers.GetXmlElement(Saml.SamlConst.cie, Saml.SamlConst.cieNamespace, ItemsChoiceType7.FiscalCode.ToString(), FiscalCode)); + { + elements.Add(ItemsChoiceType7.FiscalCode); + values.Add(this.FiscalCode); + } if (NACE2Codes.Length > 0) foreach (var code in NACE2Codes) - elements.Add(XmlHelpers.GetXmlElement(Saml.SamlConst.cie, Saml.SamlConst.cieNamespace, ItemsChoiceType7.NACE2Code.ToString(), code)); - + { + elements.Add(ItemsChoiceType7.NACE2Code); + values.Add(code); + } var extensions = new Saml.SP.ContactPersonSPExtensionType() { + Items = values.ToArray(), + ItemsElementName = elements.ToArray(), Municipality = this.Municipality, - Country = this.Country, - Any = elements.ToArray() + Country = this.Country }; if (!string.IsNullOrEmpty(this.Province)) @@ -81,11 +93,13 @@ public Saml.SP.ContactType GetContactForXml(ServiceProvider sp) { if (string.IsNullOrWhiteSpace(Municipality)) return (false, $"No {nameof(Municipality)} are specified"); + if (EmailAddress.Length == 0 || EmailAddress.Length == 1 && string.IsNullOrEmpty(EmailAddress[0])) + return (false, $"No {nameof(EmailAddress)} are specified"); return SpecificValidate(); } - public abstract List GetSpecificElements(); + public abstract (List, List) GetSpecificElements(); public abstract (bool, string) SpecificValidate(); @@ -111,17 +125,15 @@ public override (bool, string) SpecificValidate() return (true, ""); } - public override List GetSpecificElements() + public override (List, List) GetSpecificElements() { - var elements = new List(); - return elements; + return (new List(), new List()); } } public class PublicContactPerson : BaseContactPerson { - public string IPACode { get; set; } public string IPACategory { get; set; } @@ -138,15 +150,20 @@ public override (bool, string) SpecificValidate() return (true, ""); } - public override List GetSpecificElements() + public override (List, List) GetSpecificElements() { - var elements = new List(); + var elements = new List(); + var values = new List(); - elements.Add(XmlHelpers.GetXmlElement(Saml.SamlConst.cie, Saml.SamlConst.cieNamespace, ItemsChoiceType7.IPACode.ToString(), IPACode)); + elements.Add(ItemsChoiceType7.IPACode); + values.Add(this.IPACode); if (!string.IsNullOrWhiteSpace(IPACategory)) - elements.Add(XmlHelpers.GetXmlElement(Saml.SamlConst.cie, Saml.SamlConst.cieNamespace, ItemsChoiceType7.IPACategory.ToString(), IPACategory)); + { + elements.Add(ItemsChoiceType7.IPACategory); + values.Add(this.IPACategory); + } - return elements; + return (elements, values); } } } \ No newline at end of file diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/SamlHandler.cs b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/SamlHandler.cs index d77c88e..03c2617 100644 --- a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/SamlHandler.cs +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/SamlHandler.cs @@ -20,11 +20,10 @@ internal static class SamlHandler { typeof(ResponseType), new XmlSerializer(typeof(ResponseType)) }, { typeof(LogoutRequestType), new XmlSerializer(typeof(LogoutRequestType)) }, { typeof(LogoutResponseType), new XmlSerializer(typeof(LogoutResponseType)) }, + { typeof(SP.EntityDescriptorType), new XmlSerializer(typeof(SP.EntityDescriptorType)) }, }; private static readonly List listAuthRefValid = new List { - SamlConst.SpidL + "1", - SamlConst.SpidL + "2", SamlConst.SpidL + "3" }; diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/xsd/cie.xsd b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/xsd/cie.xsd index c6b8a16..cea39db 100644 --- a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/xsd/cie.xsd +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/xsd/cie.xsd @@ -11,9 +11,9 @@ - - - + + + @@ -22,14 +22,17 @@ + + + - - - + + + diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/xsd/saml-schema-metadata-sp-cie.xsd b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/xsd/saml-schema-metadata-sp-cie.xsd index 49e7269..a242cd3 100644 --- a/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/xsd/saml-schema-metadata-sp-cie.xsd +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.Authentication/Saml/xsd/saml-schema-metadata-sp-cie.xsd @@ -111,6 +111,7 @@ + diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/ServiceProvidersFactory.cs b/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/ServiceProvidersFactory.cs new file mode 100644 index 0000000..cb68b6b --- /dev/null +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/ServiceProvidersFactory.cs @@ -0,0 +1,107 @@ +using Microsoft.Extensions.Options; +using CIE.AspNetCore.Authentication.Models; +using SPIDSS = CIE.AspNetCore.Authentication.Models.ServiceProviders; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CIE.AspNetCore.Authentication.Models.ServiceProviders; + +namespace CIE.AspNetCore.WebApp +{ + public class ServiceProvidersFactory : IServiceProvidersFactory + { + private readonly CieOptions _options; + + public ServiceProvidersFactory(IOptionsMonitor options) + { + _options = options.CurrentValue; + } + + public Task> GetServiceProviders() + => Task.FromResult(new List() { + new ServiceProviderStandard() + { + FileName = "metadata.xml", + Certificate = _options.Certificate, + Id = Guid.NewGuid(), + EntityId = _options.EntityId, + SingleLogoutServiceLocations = new List() { + new SingleLogoutService() { + Location = "https://localhost:5001/signout-cie", + ProtocolBinding = ProtocolBinding.POST + } + }, + AssertionConsumerServices = new System.Collections.Generic.List() { + new AssertionConsumerService(){ + Index = 0, + IsDefault = true, + Location = "https://localhost:5001/signin-cie", + ProtocolBinding = ProtocolBinding.POST + }, + new AssertionConsumerService() { + Index = 1, + IsDefault = false, + Location = "https://localhost:5001/signin-cie", + ProtocolBinding = ProtocolBinding.Redirect + } + }, + AttributeConsumingServices = new System.Collections.Generic.List() { + new AttributeConsumingService() { + Index = 0, + ServiceDescription = "Service 1 Description", + ClaimTypes = new CieClaimTypes[] { + CieClaimTypes.Name, + CieClaimTypes.FamilyName, + CieClaimTypes.FiscalNumber, + CieClaimTypes.DateOfBirth + } + }, + new AttributeConsumingService() { + Index = 1, + ServiceDescription = "Service 2 Description", + ClaimTypes = new CieClaimTypes[] { + CieClaimTypes.Name, + CieClaimTypes.FamilyName, + CieClaimTypes.FiscalNumber, + CieClaimTypes.DateOfBirth + } + } + }, + OrganizationName = "Organizzazione fittizia per il collaudo", + OrganizationDisplayName = "Oganizzazione fittizia per il collaudo", + OrganizationURL = "https://www.asfweb.it/", + ContactPersons = new System.Collections.Generic.List() { + new PrivateContactPerson() { + ContactType = Authentication.Saml.SP.ContactTypeType.administrative, + Company = "Partner Tecnologico per Soluzioni di Identità Federata s.r.l.", + EmailAddress = new string[] { "info.cie@partnertecnologicoidfederata.com" }, + TelephoneNumber = new string[] { "+390999135792" }, + VATNumber = "IT01234567890", + FiscalCode = "9876543210", + NACE2Codes = new string[] { "CODICE_ATECO" }, + Municipality = "CODICE_ISTAT_SEDE" + }/*, + new PublicContactPerson() { + ContactType = Authentication.Saml.SP.ContactTypeType.administrative, + EmailAddress = new string[] { "esempio_sp_privato@spp.it" }, + TelephoneNumber = new string[] { "+39061234567" }, + IPACode = "codiceIPA_SP", + IPACategory = "categoriaIPA_SP", + NACE2Codes = new string[] { "CODICE_ATECO" }, + Municipality = "CODICE_ISTAT_SEDE" + }, + new PrivateContactPerson() { + ContactType = Authentication.Saml.SP.ContactTypeType.technical, + Company = "Partner Tecnologico per Soluzioni di Identità Federata s.r.l.", + EmailAddress = new string[] { "info.cie@partnertecnologicoidfederata.com" }, + TelephoneNumber = new string[] { "+390999135792" }, + VATNumber = "IT01234567890", + FiscalCode = "9876543210", + NACE2Codes = new string[] { "CODICE_ATECO" }, + Municipality = "CODICE_ISTAT_SEDE" + }*/ + } + } + }); + } +} diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/Startup.cs b/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/Startup.cs index 956543f..7384d46 100644 --- a/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/Startup.cs +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/Startup.cs @@ -37,10 +37,11 @@ public void ConfigureServices(IServiceCollection services) o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; o.DefaultChallengeScheme = CieDefaults.AuthenticationScheme; }) - .AddCie(Configuration, o => { - o.Events.OnTokenCreating = async (s) => await s.HttpContext.RequestServices.GetRequiredService().TokenCreating(s); + .AddCie(o => { o.LoadFromConfiguration(Configuration); + o.Events.OnTokenCreating = async (s) => await s.HttpContext.RequestServices.GetRequiredService().TokenCreating(s); }) + .AddServiceProvidersFactory() .AddCookie(); services.AddScoped(); } @@ -65,6 +66,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseAuthentication(); app.UseAuthorization(); + app.AddCieSPMetadataEndpoints(); + app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", diff --git a/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/appsettings.json b/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/appsettings.json index 4e831b4..de44415 100644 --- a/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/appsettings.json +++ b/CIE.AspNetCore.Authentication/CIE.AspNetCore.WebApp/appsettings.json @@ -21,7 +21,7 @@ "SecurityLevel": 3 }, "Certificate": { - "Source": "Raw", + "Source": "File", "Store": { "Location": "CurrentUser", "Name": "My", @@ -30,16 +30,16 @@ "validOnly": false }, "File": { - "Path": "xxx.pfx", - "Password": "xxx" + "Path": "wwwroot/cie/ComuneVigata-SPID.pfx", + "Password": "P@ssW0rd!" }, "Raw": { - "Certificate": "test", - "Password": "test" + "Certificate": "base64", + "Password": "password" } }, - "EntityId": "https://entityID", - "AssertionConsumerServiceIndex": 2, + "EntityId": "https://entityID/ENTE_TEST", + "AssertionConsumerServiceIndex": 0, "AttributeConsumingServiceIndex": 0 }, "AllowedHosts": "*" diff --git a/README.md b/README.md index c894eeb..0545082 100644 --- a/README.md +++ b/README.md @@ -161,8 +161,120 @@ public class CustomCieEvents : CieEvents } ``` +# Generazione Metadata Service Provider +La libreria è dotata della possibilità di generare dinamicamente dei metadata per Service Provider conformi ai profili privati e pubblici indicati nel **Manuale Tecnico** CIE. + +E' possibile aggiungere nuovi ServiceProvider sia in maniera procedurale, in fase di `Startup`, come segue: + +```csharp +.AddSpid(o => +{ + o.LoadFromConfiguration(Configuration); + o.ServiceProviders.AddRange(GetServiceProviders(o)); +}) + +...... + +private List GetServiceProviders(SpidOptions o) +{ + return new List(){ + new ServiceProviderStandard() + { + FileName = "metadata.xml", + Certificate = _options.Certificate, + Id = Guid.NewGuid(), + EntityId = _options.EntityId, + SingleLogoutServiceLocations = new List() { + new SingleLogoutService() { + Location = "https://localhost:5001/signout-cie", + ProtocolBinding = ProtocolBinding.POST + } + }, + AssertionConsumerServices = new System.Collections.Generic.List() { + new AssertionConsumerService(){ + Index = 0, + IsDefault = true, + Location = "https://localhost:5001/signin-cie", + ProtocolBinding = ProtocolBinding.POST + }, + new AssertionConsumerService() { + Index = 1, + IsDefault = false, + Location = "https://localhost:5001/signin-cie", + ProtocolBinding = ProtocolBinding.Redirect + } + }, + AttributeConsumingServices = new System.Collections.Generic.List() { + new AttributeConsumingService() { + Index = 0, + ServiceDescription = "Service 1 Description", + ClaimTypes = new CieClaimTypes[] { + CieClaimTypes.Name, + CieClaimTypes.FamilyName, + CieClaimTypes.FiscalNumber, + CieClaimTypes.DateOfBirth + } + }, + new AttributeConsumingService() { + Index = 1, + ServiceDescription = "Service 2 Description", + ClaimTypes = new CieClaimTypes[] { + CieClaimTypes.Name, + CieClaimTypes.FamilyName, + CieClaimTypes.FiscalNumber, + CieClaimTypes.DateOfBirth + } + } + }, + OrganizationName = "Organizzazione fittizia per il collaudo", + OrganizationDisplayName = "Oganizzazione fittizia per il collaudo", + OrganizationURL = "https://www.asfweb.it/", + ContactPersons = new System.Collections.Generic.List() { + new PrivateContactPerson() { + ContactType = Authentication.Saml.SP.ContactTypeType.administrative, + Company = "Partner Tecnologico per Soluzioni di Identità Federata s.r.l.", + EmailAddress = new string[] { "info.cie@partnertecnologicoidfederata.com" }, + TelephoneNumber = new string[] { "+390999135792" }, + VATNumber = "IT01234567890", + FiscalCode = "9876543210", + NACE2Codes = new string[] { "CODICE_ATECO" }, + Municipality = "CODICE_ISTAT_SEDE" + } + } + }, +....... +``` +sia utilizzando una classe che implementa l'interfaccia `IServiceProvidersFactory` e configurandola come segue: + +```csharp +.AddSpid(o => +{ + o.LoadFromConfiguration(Configuration); +}) +.AddServiceProvidersFactory(); + +........ + +public class ServiceProvidersFactory : IServiceProvidersFactory +{ + public Task> GetServiceProviders() + => Task.FromResult(new List() { + new Authentication.Models.ServiceProviders.ServiceProviderStandard() + { +.............. +``` + +Infine, per poter esporre gli endpoint dei metadata relativi ai Service Provider registrati, sarà necessario aggiungere la seguente riga: +```csharp +app.AddSpidSPMetadataEndpoints(); +``` + +Tutti i metadata generati vengono automaticamente esposti su endpoint diversi, che hanno come BasePath `/metadata-cie` (ad esempio, un metadata definito con NomeFile = `metadata.xml` verrà esposto sull'endpoint `/metadata-cie/metadata.xml`): il BasePath può essere cambiato, sovrascrivendo la proprietà `ServiceProvidersMetadataEndpointsBasePath` sulle SpidOptions nello `Startup.cs`. + +All'interno dell'esempio `CIE.AspNetCore.WebApp` è presente un ServiceProvider di esempio configurato tramite `IServiceProvidersFactory`. + # Error Handling -La libreria può, in qualunque fase (sia in fase di creazione della Request sia in fase di gestione della Response), sollevare eccezioni. +La libreria può, in qualunque fase (sia in fase di creazione della Request sia in fase di gestione della Response), sollevare eccezioni. Un tipico scenario è quello in cui vengono ricevuti i codici di errore previsti dal protocollo (n.19, n.20, ecc....), in tal caso la libreria solleva un'eccezione contenente il corrispondente messaggio d'errore localizzato, richiesto dalle specifiche CIE3.0, che è possibile gestire (ad esempio per la visualizzazione) utilizzando il normale flusso previsto per AspNetCore. L'esempio seguente fa uso del middleware di ExceptionHandling di AspNetCore. ```csharp