I can't find how to use the .NET Client for Azure search to add a scoring profile. Yes, I know there's a doc to do it using the REST API
Thanks.
The scoring profiles have to be created at the same time as the index:
private async Task CreateIndexAsync<T>(string index) where T : class
{
var definition = new Index()
{
Name = index,
Fields = FieldBuilder.BuildForType<T>(),
ScoringProfiles = new List<ScoringProfile>
{
//your scoring profiles here
}
};
if (!_adminServiceClient.Indexes.Exists(index))
{
await _adminServiceClient.Indexes.CreateAsync(definition);
}
}
If you are using v11 of Azure search library for .Net you can add scoring profile to the search index like this:
var fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(MyType));
var definition = new SearchIndex(name: "MyIndexName", searchFields);
definition.ScoringProfiles.Add(new ScoringProfile(name: "default")
{
// Your scoring profile definition
}
await _searchIndexClient.CreateIndexAsync(definition);
Related
I am using Azure AD B2C. I was hoping to get a count of the number of users - however, according to the documentation:
Azure AD B2C currently does not support advanced query capabilities on
directory objects. This means that there is no support for $count ...
Great, so I thought the next best thing to do was to perform a query to get all the ids of the users, i.e.:
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential( tenant_id, client_id, client_secret, options );
var scopes = new[] { "https://graph.microsoft.com/.default" };
var users = await graphClient.Users
.Request()
.Select( "id" )
.GetAsync();
// This shows other properties returned in addition to "id" ...
if ( users is not null )
{
Console.WriteLine( JsonSerializer.Serialize( users ) );
}
I was under the impression (from the documentation) that using .Select( "id" ) would only return the "id" property, instead, the graph client returns what I assume are a default set of properties per user in addition to the "id", i.e.:
[
{"accountEnabled":null,
"ageGroup":null,
...more properties...,
"id":"xxxx...",
"#odata.type":"microsoft.graph.user"
},
{"accountEnabled":null,
"ageGroup":null,
... more properties ...,
"id":"xxxx...",
"#odata.type":"microsoft.graph.user"
}, ...
]
I am probably doing something wrong, but just how do you return ONLY the property (in this case "id") WITHOUT any other properties? I have yet to find an example or documentation that describes how to do this.
Thanks in advance.
Thanks to #user2250152 and #Rukmini - the "answer" per se is that the graph QUERY returns only the selected / requested properties, that is, when the following code is used:
var users = await graphClient.Users
.Request()
.Select("id")
.GetAsync();
The QUERY returns the "ids" as illustrated in #Rukmini's answer, but then the graphClient populates a list of "User" objects that is returned when the call completes. The User objects will contain the Id property as well as all the other properties associated with a User object as mentioned by #user2250152.
Thanks again for clearing this up.
I tried to reproduce the same in my environment and got the results successfully like below:
To get the Azure AD b2c User IDs I used the below code:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "b2corg.onmicrosoft.com";
var clientId = "ClientID";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var userName = "admin#b2corg.onmicrosoft.com";
var password = "password";
var userNamePasswordCredential = new UsernamePasswordCredential(
userName, password, tenantId, clientId, options);
var graphClient = new GraphServiceClient(userNamePasswordCredential, scopes);
var users = await graphClient.Users
.Request()
.Select("id")
.GetAsync();
users.ToList().ForEach(x => { Console.WriteLine("\nid: " + x.Id); });
I also tried in the Microsoft Graph Explorer, and I am able to fetch only the User IDs like below:
https://graph.microsoft.com/v1.0/users?$select=id
I am trying to add the creation of roles while I create a new Tenant from the UI on ABP.IO Framework version 4.
From ABP.IO documentation, I found that by using the existing class SaasDataSeedContributor I can "seed" some datas while I am creating a new Tenant.
My issue is that from this class, I do not have permission to use IIdentityRoleAppService.CreateAsync method (Given policy has not granted).
So I tried to go through an AppService and use IdentityRoleManager or even IIdentityRoleRepository,but it is not possible to create IdentityRole object as the constructor is inaccessible due to his protection level.
Any thought about it? Is there any another way to do action while creating a tenant appart using SaasDataSeedContributor. Or maybe I am doing something wrong here.
Thanks for your help
Try this.
public class AppRolesDataSeedContributor : IDataSeedContributor, ITransientDependency
{
private readonly IGuidGenerator _guidGenerator;
private readonly IdentityRoleManager _identityRoleManager;
public AppRolesDataSeedContributor(IGuidGenerator guidGenerator, IdentityRoleManager identityRoleManager)
{
_guidGenerator = guidGenerator;
_identityRoleManager = identityRoleManager;
}
public async Task SeedAsync(DataSeedContext context)
{
if (context.TenantId.HasValue)
{
// try this for a single known role
var role = await _identityRoleManager.FindByNameAsync("new_role");
if (role == null)
{
var identityResult = await _identityRoleManager.CreateAsync(
new IdentityRole(_guidGenerator.Create(), "new_role", context.TenantId.Value));
}
// or this (not tested) for multiple roles
/*
var newRoles = new[] { "role1", "role2" };
var identityRoles = from r
in _identityRoleManager.Roles
where r.TenantId == context.TenantId.Value
select r.Name;
var except = newRoles.Except(identityRoles.ToList());
foreach (var name in except)
{
var identityResult = await _identityRoleManager.CreateAsync(
new IdentityRole(_guidGenerator.Create(), name, context.TenantId.Value));
}
*/
}
}
}
In the Azure AD, I have created a Native application and assigned users to it. I want to create an extension property using Microsoft Graph's SchemaExtension. I have written the code for the same and it does not throw any errors and the extension is also added(able to see the Id) through code. I then add the value of the custom property for a user under the application but am not able to view in the Azure portal or in the code when I do the below:
I have supplied the value for the custom property after updating the status of the schema extensions to "Available" but still not able to fetch the custom property value in code or in the UI. Please provide inputs if any.
Below is the code i am using:
var aap = new AzureAuthenticationProvider();
var graphserviceClient = new GraphServiceClient(aap);
IEnumerable<string> targetType = new string[] { "User" };
SchemaExtension extensionDefinition = new SchemaExtension
{
ODataType = "microsoft.graph.ComplexExtensionValue",
Id = $"crmCompanyId",
Description = "This extension is for company id",
Owner = "fac4cdd3-1015-4ed5-8a7b-6008095750e6",
Properties = new List<ExtensionSchemaProperty>()
{
new ExtensionSchemaProperty() { Name = "cid", Type = "String" }
},
Status = "InDevelopment",
TargetTypes = targetType
};
SchemaExtension schemaExtension = //code to add extensionDefinition
var updateSchemaExtension = new SchemaExtension
{
Status = "Available"
};
//update schema to available status
var updatedSchema = code to update extension
IDictionary<string, object> extensionInstance = new Dictionary<string,object>();
extensionInstance.Add(schemaExtension.Id, new
MyDBExtensionClass("testMyExtension"));
User updateUser = new User()
{
AdditionalData = extensionInstance
};
var updatedUser = await graphserviceClient.Users["9ca2bb42-a7f8-487c-
87a0 - 38513057886d"].Request().UpdateAsync(updateUser);//AzureADGraphUser3
- 9ca2bb42 - a7f8 - 487c - 87a0 - 38513057886d
await Task.Delay(10000);
Could you please review the code and let me know what I am missing...I have referred the comments at https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/151 enter code hereby Michael Maier. Let me know in case I need to provide any further info.
Thanks
I was finally able to view the property i had set once i found this link:https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/238
I queried the extension property on a user this way and was able to view the property value i set.
I am trying to get user's group information who log-Ins into the application.
Using below code, when I am hitting https://graph.microsoft.com/v1.0/users/{user}, then I am able to see that user is exist (200), but when trying to hit https://graph.microsoft.com/v1.0/users/{user}/memberOf, then I am getting 403.
private static async Task Test()
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TOKEN HERE");
var user = "testuser#onmicrosoft.com";
var userExist = await DoesUserExistsAsync(client, user);
Console.WriteLine($"Does user exists? {userExist}");
if (userExist)
{
var groups = await GetUserGroupsAsync(client, user);
foreach (var g in groups)
{
Console.WriteLine($"Group: {g}");
}
}
}
}
private static async Task<bool> DoesUserExistsAsync(HttpClient client, string user)
{
var payload = await client.GetStringAsync($"https://graph.microsoft.com/v1.0/users/{user}");
return true;
}
private static async Task<string[]> GetUserGroupsAsync(HttpClient client, string user)
{
var payload = await client.GetStringAsync($"https://graph.microsoft.com/v1.0/users/{user}/memberOf");
var obj = JsonConvert.DeserializeObject<JObject>(payload);
var groupDescription = from g in obj["value"]
select g["displayName"].Value<string>();
return groupDescription.ToArray();
}
Is this something related to permission issue, my token has below scope now,
Note - Over here I am not trying to access other user/group information, only who log-ins. Thanks!
Calling /v1.0/users/[a user]/memberOf requires your access token to have either Directory.Read.All, Directory.ReadWrite.All or Directory.AccessAsUser.All and this is
documented at https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list_memberof.
A great way to test this API call before implementing it in code is to use the Microsoft Graph explorer where you can change which permissions your token has by using the "modify permissions" dialog.
I have an API that uses IdentityServer4 for token validation.
I want to unit test this API with an in-memory TestServer. I'd like to host the IdentityServer in the in-memory TestServer.
I have managed to create a token from the IdentityServer.
This is how far I've come, but I get an error "Unable to obtain configuration from http://localhost:54100/.well-known/openid-configuration"
The Api uses [Authorize]-attribute with different policies. This is what I want to test.
Can this be done, and what am I doing wrong?
I have tried to look at the source code for IdentityServer4, but have not come across a similar integration test scenario.
protected IntegrationTestBase()
{
var startupAssembly = typeof(Startup).GetTypeInfo().Assembly;
_contentRoot = SolutionPathUtility.GetProjectPath(#"<my project path>", startupAssembly);
Configure(_contentRoot);
var orderApiServerBuilder = new WebHostBuilder()
.UseContentRoot(_contentRoot)
.ConfigureServices(InitializeServices)
.UseStartup<Startup>();
orderApiServerBuilder.Configure(ConfigureApp);
OrderApiTestServer = new TestServer(orderApiServerBuilder);
HttpClient = OrderApiTestServer.CreateClient();
}
private void InitializeServices(IServiceCollection services)
{
var cert = new X509Certificate2(Path.Combine(_contentRoot, "idsvr3test.pfx"), "idsrv3test");
services.AddIdentityServer(options =>
{
options.IssuerUri = "http://localhost:54100";
})
.AddInMemoryClients(Clients.Get())
.AddInMemoryScopes(Scopes.Get())
.AddInMemoryUsers(Users.Get())
.SetSigningCredential(cert);
services.AddAuthorization(options =>
{
options.AddPolicy(OrderApiConstants.StoreIdPolicyName, policy => policy.Requirements.Add(new StoreIdRequirement("storeId")));
});
services.AddSingleton<IPersistedGrantStore, InMemoryPersistedGrantStore>();
services.AddSingleton(_orderManagerMock.Object);
services.AddMvc();
}
private void ConfigureApp(IApplicationBuilder app)
{
app.UseIdentityServer();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var options = new IdentityServerAuthenticationOptions
{
Authority = _appsettings.IdentityServerAddress,
RequireHttpsMetadata = false,
ScopeName = _appsettings.IdentityServerScopeName,
AutomaticAuthenticate = false
};
app.UseIdentityServerAuthentication(options);
app.UseMvc();
}
And in my unit-test:
private HttpMessageHandler _handler;
const string TokenEndpoint = "http://localhost/connect/token";
public Test()
{
_handler = OrderApiTestServer.CreateHandler();
}
[Fact]
public async Task LeTest()
{
var accessToken = await GetToken();
HttpClient.SetBearerToken(accessToken);
var httpResponseMessage = await HttpClient.GetAsync("stores/11/orders/asdf"); // Fails on this line
}
private async Task<string> GetToken()
{
var client = new TokenClient(TokenEndpoint, "client", "secret", innerHttpMessageHandler: _handler);
var response = await client.RequestClientCredentialsAsync("TheMOON.OrderApi");
return response.AccessToken;
}
You were on the right track with the code posted in your initial question.
The IdentityServerAuthenticationOptions object has properties to override the default HttpMessageHandlers it uses for back channel communication.
Once you combine this with the CreateHandler() method on your TestServer object you get:
//build identity server here
var idBuilder = new WebBuilderHost();
idBuilder.UseStartup<Startup>();
//...
TestServer identityTestServer = new TestServer(idBuilder);
var identityServerClient = identityTestServer.CreateClient();
var token = //use identityServerClient to get Token from IdentityServer
//build Api TestServer
var options = new IdentityServerAuthenticationOptions()
{
Authority = "http://localhost:5001",
// IMPORTANT PART HERE
JwtBackChannelHandler = identityTestServer.CreateHandler(),
IntrospectionDiscoveryHandler = identityTestServer.CreateHandler(),
IntrospectionBackChannelHandler = identityTestServer.CreateHandler()
};
var apiBuilder = new WebHostBuilder();
apiBuilder.ConfigureServices(c => c.AddSingleton(options));
//build api server here
var apiClient = new TestServer(apiBuilder).CreateClient();
apiClient.SetBearerToken(token);
//proceed with auth testing
This allows the AccessTokenValidation middleware in your Api project to communicate directly with your In-Memory IdentityServer without the need to jump through hoops.
As a side note, for an Api project, I find it useful to add IdentityServerAuthenticationOptions to the services collection in Startup.cs using TryAddSingleton instead of creating it inline:
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton(new IdentityServerAuthenticationOptions
{
Authority = Configuration.IdentityServerAuthority(),
ScopeName = "api1",
ScopeSecret = "secret",
//...,
});
}
public void Configure(IApplicationBuilder app)
{
var options = app.ApplicationServices.GetService<IdentityServerAuthenticationOptions>()
app.UseIdentityServerAuthentication(options);
//...
}
This allows you to register the IdentityServerAuthenticationOptions object in your tests without having to alter the code in the Api project.
I understand there is a need for a more complete answer than what #james-fera posted. I have learned from his answer and made a github project consisting of a test project and API project. The code should be self-explanatory and not hard to understand.
https://github.com/emedbo/identityserver-test-template
The IdentityServerSetup.cs class https://github.com/emedbo/identityserver-test-template/blob/master/tests/API.Tests/Config/IdentityServerSetup.cs can be abstracted away e.g. NuGetted away, leaving the base class IntegrationTestBase.cs
The essences is that can make the test IdentityServer work just like a normal IdentityServer, with users, clients, scopes, passwords etc. I have made the DELETE method [Authorize(Role="admin)] to prove this.
Instead of posting code here, I recommend read #james-fera's post to get the basics then pull my project and run tests.
IdentityServer is such a great tool, and with the ability to use the TestServer framework it gets even better.
I think you probably need to make a test double fake for your authorization middleware depending on how much functionality you want. So basically you want a middleware that does everything that the Authorization middleware does minus the back channel call to the discovery doc.
IdentityServer4.AccessTokenValidation is a wrapper around two middlewares. The JwtBearerAuthentication middleware, and the OAuth2IntrospectionAuthentication middleware. Both of these grab the discovery document over http to use for token validation. Which is a problem if you want to do an in-memory self-contained test.
If you want to go through the trouble you will probably need to make a fake version of app.UseIdentityServerAuthentication that doesnt do the external call that fetches the discovery document. It only populates the HttpContext principal so that your [Authorize] policies can be tested.
Check out how the meat of IdentityServer4.AccessTokenValidation looks here. And follow up with a look at how JwtBearer Middleware looks here
We stepped away from trying to host a mock IdentityServer and used dummy/mock authorizers as suggested by others here.
Here's how we did that in case it's useful:
Created a function which takes a type, creates a test Authentication Middleware and adds it to the DI engine using ConfigureTestServices (so that it's called after the call to Startup.)
internal HttpClient GetImpersonatedClient<T>() where T : AuthenticationHandler<AuthenticationSchemeOptions>
{
var _apiFactory = new WebApplicationFactory<Startup>();
var client = _apiFactory
.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication("Test")
.AddScheme<AuthenticationSchemeOptions, T>("Test", options => { });
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");
return client;
}
Then we create what we called 'Impersonators' (AuthenticationHandlers) with the desired roles to mimic users with roles (We actually used this as a base class, and create derived classes based on this to mock different users):
public abstract class FreeUserImpersonator : AuthenticationHandler<AuthenticationSchemeOptions>
{
public Impersonator(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
base.claims.Add(new Claim(ClaimTypes.Role, "FreeUser"));
}
protected List<Claim> claims = new List<Claim>();
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Test");
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
Finally, we can perform our integration tests as follows:
// Arrange
HttpClient client = GetImpersonatedClient<FreeUserImpersonator>();
// Act
var response = await client.GetAsync("api/things");
// Assert
Assert.That.IsSuccessful(response);
Test API startup:
public class Startup
{
public static HttpMessageHandler BackChannelHandler { get; set; }
public void Configuration(IAppBuilder app)
{
//accept access tokens from identityserver and require a scope of 'Test'
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost",
BackchannelHttpHandler = BackChannelHandler,
...
});
...
}
}
Assigning the AuthServer.Handler to TestApi BackChannelHandler in my unit test project:
protected TestServer AuthServer { get; set; }
protected TestServer MockApiServer { get; set; }
protected TestServer TestApiServer { get; set; }
[OneTimeSetUp]
public void Setup()
{
...
AuthServer = TestServer.Create<AuthenticationServer.Startup>();
TestApi.Startup.BackChannelHandler = AuthServer.CreateHandler();
TestApiServer = TestServer.Create<TestApi.Startup>();
}
The trick is to create a handler using the TestServer that is configured to use IdentityServer4. Samples can be found here.
I created a nuget-package available to install and test using the Microsoft.AspNetCore.Mvc.Testing library and the latest version of IdentityServer4 for this purpose.
It encapsulates all the infrastructure code necessary to build an appropriate WebHostBuilder which is then used to create a TestServer by generating the HttpMessageHandler for the HttpClient used internally.
None of the other answers worked for me because they rely on 1) a static field to hold your HttpHandler and 2) the Startup class to have knowledge that it may be given a test handler. I've found the following to work, which I think is a lot cleaner.
First create an object that you can instantiate before your TestHost is created. This is because you won't have the HttpHandler until after the TestHost is created, so you need to use a wrapper.
public class TestHttpMessageHandler : DelegatingHandler
{
private ILogger _logger;
public TestHttpMessageHandler(ILogger logger)
{
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_logger.Information($"Sending HTTP message using TestHttpMessageHandler. Uri: '{request.RequestUri.ToString()}'");
if (WrappedMessageHandler == null) throw new Exception("You must set WrappedMessageHandler before TestHttpMessageHandler can be used.");
var method = typeof(HttpMessageHandler).GetMethod("SendAsync", BindingFlags.Instance | BindingFlags.NonPublic);
var result = method.Invoke(this.WrappedMessageHandler, new object[] { request, cancellationToken });
return await (Task<HttpResponseMessage>)result;
}
public HttpMessageHandler WrappedMessageHandler { get; set; }
}
Then
var testMessageHandler = new TestHttpMessageHandler(logger);
var webHostBuilder = new WebHostBuilder()
...
services.PostConfigureAll<JwtBearerOptions>(options =>
{
options.Audience = "http://localhost";
options.Authority = "http://localhost";
options.BackchannelHttpHandler = testMessageHandler;
});
...
var server = new TestServer(webHostBuilder);
var innerHttpMessageHandler = server.CreateHandler();
testMessageHandler.WrappedMessageHandler = innerHttpMessageHandler;