Sense/net using content query in web application - sensenet

I try to use content query in web application but it throw an exception " Lucene.Net.Store.AlreadyClosedException: this IndexReader is closed". Please give help me resolve that problem.
var startSettings = new RepositoryStartSettings
{
Console = Console.Out,
StartLuceneManager = true, // <-- this is necessary
IsWebContext = false,
PluginsPath = AppDomain.CurrentDomain.BaseDirectory,
};
using (Repository.Start(startSettings))
{
var resultQuery = ContentQuery.Query("+InTree:#0 + DisplayName:*#1*", null, folderPath, q);
}

The recommended way to connect to Sense/Net from a different application (app domain) is through the REST API. It is much easier to maintain and involves less configuration (the only exception is where you are working inside the Sense/Net application itself, or you only have a single application and you do not want to access Sense/Net from anywhere else, and you are willing to deal with a local index of Sense/Net and all the config values it needs, etc).
Connecting through the REST API does not mean you have to send HTTP requests manually (although that is also not complicated at all): there is a .Net client library which does that for you. You can access all content metadata or binaries through the client, you can upload files, query content, manage permissions, etc.
// loading a content
dynamic content = await Content.LoadAsync(id);
DateTime date = content.BirthDate;
// querying
var results = await Content.QueryAsync(queryText);
Install: https://www.nuget.org/packages/SenseNet.Client
Source and examples: https://github.com/SenseNet/sn-client-dotnet
To use it in a web application, you have to do the following:
initialize the client context once, at the beginning of the application life cycle (e.g. app start)
if you need to make requests to Sense/Net in the name of the currently logged in user (e.g. because you want to query for documents accessible by her), than you have to create a new ServerContext object for every user with the username/password of that user, and provide this object to any client call (e.g. load or save content methods).
var sc = new ServerContext
{
Url = "http://example.com",
Username = "user1",
Password = "asdf"
};
var content = await Content.LoadAsync(id, sc);

Related

Graph API Call Issues - POST Event

I'm running into issues when trying to create an event in a specific user calendar.
This call works fine: POST https://graph.microsoft.com/v1.0/me/events
But when I change the API Call to include the other user details, it throws this error: "The specified object was not found in the store."
I have created an app on Azure and assigned all necessary permissions.
App Permissions
Error:
Can someone please assist if I'm missing something?
Please note when you use /me, it means you are calling the ms graph api with a delegate api permission which is authentiated by entering user name/password, you can only do operations on your own account with this kind of authentication. While you want to do operations for other users like /users/user_id/xxx, you required the application api permission. That's why api document showed api permission in Delegated and Application. One for personal and another for all users.
When we need to get access token contain application permission, we need to use client credential flow. This flow is used for daemon application since this kind of application doesn't have user interactive operation, so we can only use application permission for this kind of scenario. And as you can see it will offer "very big ability" to the application(allow application to create/change/delete items for any user in your tenant), so we need to use appliation permission with caution.
Come back to the case, you can follow this section to generate access token and call the api. You can also using graph SDK in your code to call that api.
using Azure.Identity;
using Microsoft.Graph;
public async Task<string> testAsync() {
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "azure_ad_clientid";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var a = await graphClient.Users["user_id"].Request().GetAsync();
return a.DisplayName;
}

AspNetCore: How to mock external authentication / Microsoft account for integration tests?

I have an OpenID Connect / OAuth2 server (IdP) in my application stack. IdP allows both local and external authentication.
I have integration tests covering most scenarios, but struggle to create a end-to-end test for an external authentication scenario. There are multiple external providers, but from my application perspective they are all using the same workflow over OpenID Connect, only have slight difference (parameters, ie. redirect uri, scheme name, etc.). So it is enough to test one of them. One of them is Microsoft Account (aka. Azure AD)
Integration test is based on WebApplicationFactory (in-memory server with corresponding HttpClient). Local authentication is quite easy, because the whole part runs in my application domain, have access to full source code, etc. I simply create a request to the authorization endpoint and post back user credentials when prompted (I still need to parse the login page to retrieve the antiforgery token, but that's doable)
But when it comes to external, for example Microsoft Account, login involves multiple steps via AJAX and the final post with over 10 parameters, which I unable to reverse engenineer. Other provider has also same level of difficulty.
Since external providers are just blackboxes, from my IdP's perspective, it's just issuing a challenge (redirect to external authorization) and pick up after redirect. Is there a good way to mock the "in between" part?
My solution was to create a middleware, which will mock the external authentication. And then re-configure options for the external authentication scheme to direct to the path middleware is handling. You may also want to overwrite the signingkey (or turn of signature validation). So this code goes to WebApplicationFactory's ConfigureServices/ConfigureTestServices (etc., depending on your setup), to override original setup:
services.AddTransient<IStartupFilter, FakeExternalAuthenticationStartupFilter>();
services.Configure(AuthenticationSchemes.ExternalMicrosoft, (OpenIdConnectOptions options) =>
{
options.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = FakeExternalAuthenticationStartupFilter.AuthorizeEndpoint,
};
options.TokenValidationParameters.IssuerSigningKey = FakeExternalAuthenticationStartupFilter.SecurityKey;
});
Remark: WebApplicationFactory does not provide a way to override IApplicationBuilder (middleware) stack, so need to add IStartupFilter
The middleware then needs to issue a token with the security key and issue a form post back to the redirect uri. The usual way to achieve this to return simple HTML page with a form which will submit itself once loaded. This works fine in browsers, but HttpClient won't do anything, so the test have to parse the response and create a post request manually.
While this is doable, I wanted to spare this extra step, having to parse respond and re-send it, and make it a single step. Difficulties were:
redirect is not possible (starts as GET request, should ended as POST, need also form data)
cookies issued by OpenIdConnectHandler before redirecting (correlation and nonce) necessary to restore state, only available at redirect uri path (Set-Cookie with path=)
My solution was creating a middleware handling authorization (GET) requests at the same path as the redirect uri is set up, issue token and rewrite request so that OpenIdConnectHandler would pick up. Here's middleware's Invoke method:
public async Task Invoke(HttpContext httpContext)
{
if (!HttpMethods.IsGet(httpContext.Request.Method) || !httpContext.Request.Path.StartsWithSegments(AuthorizeEndpoint))
{
await _next(httpContext);
return;
}
// get and validate query parameters
// Note: these are absolute minimal, might need to add more depending on your flow logic
var clientId = httpContext.Request.Query["client_id"].FirstOrDefault();
var state = httpContext.Request.Query["state"].FirstOrDefault();
var nonce = httpContext.Request.Query["nonce"].FirstOrDefault();
if (clientId is null || state is null || nonce is null)
{
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
var token = CreateToken(clientId, state, nonce); // CreateToken implementation omitted, use same signing key as used above
httpContext.Request.Method = HttpMethods.Post;
httpContext.Request.QueryString = QueryString.Empty;
httpContext.Request.ContentType = "application/x-www-form-urlencoded";
var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
["id_token"] = token,
["token_type"] = "Bearer",
["expires_in"] = "3600",
["state"] = state,
});
using var buffer = new MemoryStream();
await content.CopyToAsync(buffer, httpContext.RequestAborted);
buffer.Seek(offset: 0, loc: SeekOrigin.Begin);
var oldBody = httpContext.Request.Body;
httpContext.Request.Body = buffer;
await _next(httpContext);
httpContext.Request.Body = oldBody;
}

How to create a event in MS Teams Calendar using MS Graph API from a backend service

I am a newbie in using Microsoft Graph API, I really interested to implement the graph API possibilities in my .Net Core application. I decided to create a sample application, that create meetings in MS Teams app. I have already done the steps listed below.
I register a new app in Azure Active Directory.
Assign 'Calendars.Read' and 'Calendars.ReadWrite' (Permission type - Application) permissions.
I know there are two types of authentication for permission. Delegated and Application.
Permissions
Code
try
{
var config = this.LoadAppSettings();
GraphServiceClient graphClient = GetAuthenticatedGraphClient(config);
var #event = new Event
{
Subject = "My event by ragesh",
Start = new DateTimeTimeZone
{
DateTime = "2020-06-11T07:44:21.358Z",
TimeZone = "UTC"
},
End = new DateTimeTimeZone
{
DateTime = "2020-06-18T07:44:21.358Z",
TimeZone = "UTC"
}
};
await graphClient.Me.Events
.Request()
.AddAsync(#event);
}
catch (Exception ex)
{
throw ex;
}
But when I execute my code to create events in graph API it shows an authentication error.
Error
When you use application permissions, you cannot use the /me API url segment, since there is no authenticated user. You must instead use /users/<user-id> in it's place.
You're using the .NET SDK, so that translates to you cannot use graphClient.Me, you must use graphClient.Users[userId].

Task Module call from Ms Teams in Bot Framework

I am looking to open a task module (Pop up - iframe with audio/video) in my bot that is connected to Teams channel. I am having issues following the sample code provided on the GitHub page.
I have tried to follow the sample and incorporate to my code by did not succeed.
In my bot.cs file I am creating card action of invoke type:
card.Buttons.Add(new CardAction("invoke", TaskModuleUIConstants.YouTube.ButtonTitle, null,null,null,
new Teams.Samples.TaskModule.Web.Models.BotFrameworkCardValue<string>()
{
Data = TaskModuleUIConstants.YouTube.Id
}));
In my BotController.cs that inherits from Controller
[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await _adapter.ProcessAsync(Request, Response, _bot);
}
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Invoke)
{
return HandleInvokeMessages(activity);
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
private HttpResponseMessage HandleInvokeMessages (Activity activity)
{
var activityValue = activity.Value.ToString();
if (activity.Name == "task/fetch")
{
var action = Newtonsoft.Json.JsonConvert.DeserializeObject<Teams.Samples.TaskModule.Web.Models.BotFrameworkCardValue<string>>(activityValue);
Teams.Samples.TaskModule.Web.Models.TaskInfo taskInfo = GetTaskInfo(action.Data);
Teams.Samples.TaskModule.Web.Models.TaskEnvelope taskEnvelope = new Teams.Samples.TaskModule.Web.Models.TaskEnvelope
{
Task = new Teams.Samples.TaskModule.Web.Models.Task()
{
Type = Teams.Samples.TaskModule.Web.Models.TaskType.Continue,
TaskInfo = taskInfo
}
};
return msg;
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
There is more code as per the GitHub sample but I won't paste it here. Can someone point me into the correct direction ?
I have got to the stage that it is displaying a pop up window but the content and title comes from manifest file instead of creating actual iframe also no video is rendering. My goal is to render video within my teams using iframe container.
The important part from the sample:
This sample is deployed on Microsoft Azure and you can try it yourself by uploading Task Module CSharp.zip to one of your teams and/or as a personal app. (Sideloading must be enabled for your tenant; see step 6 here.) The app is running on the free Azure tier, so it may take a while to load if you haven't used it recently and it goes back to sleep quickly if it's not being used, but once it's loaded it's pretty snappy.
So,
Your Teams Admin MUST enable sideloading
Your bot MUST be sideloaded into Teams
The easiest way to do this would be download the sample manifest, open it in App Studio, then edit your bot information in. You then need to make sure Domains and permissions > Valid Domains are set for your bot. Also ensure you change the Tabs URLs to your own.
You also need to make sure that in your Tasks, the URLs they call ALL use https and not http. If anywhere in the chain is using http (like if you're using ngrok and http://localhost), it won't work.

IdentityServer4 handle multiple facebook AppIds for tenancy

We are adding tenancy to our IdentityServer4 (https://github.com/IdentityServer/IdentityServer4) implementation. So far everything is good. AspIdentity appends a prefix to the username based on the hostname on the request ie. sso.domain1.com and sso.domain2.com create tenancy in the identity database. The external oauth for google works fine as well as google's API console allows multiple websites to access the same AppId. Facebook, on the other hand, only allows one domain per AppId. The external providers are registered during the application startup so this presents a problem as we need to determine the correct Facebook AppId to use per request based on the hostname.
Any suggestions on the appropriate way to handle this scenario? I tried having all Facebook AppIds registered at startup and letting the login page UI determine which Facebook button to make visible. IdentityServer threw an exception for this as it doesn't allow multiple providers with the same scheme name.
Is there somewhere in the pipeline we could overload to pass in the Request host and change the External Provider AppId per request?
Update 1:
Based on McGuireV10 answer I was able to get closer to the goal. The issue now is that in the event I can set the ClientId and ClientSecret option properties, but that doesn't change the Uri that was generated for the RequestUri property. Should I be doing it a different way or do I need to rebuild the context so it regenerates the RedirectUri? I've been trying to go through Microsoft's Security source code, but haven't been able to find this yet. Ideas?
services.AddAuthentication().AddFacebook(externalAuthentication.Name, options => {
options.SignInScheme = externalAuthentication.SignInScheme;
options.ClientId = externalAuthentication.DefaultClientId;
options.ClientSecret = externalAuthentication.DefaultClientSecret;
options.RemoteAuthenticationTimeout = TimeSpan.FromMinutes(5);
options.Events = new Microsoft.AspNetCore.Authentication.OAuth.OAuthEvents {
OnRedirectToAuthorizationEndpoint = context => {
var tenancySetting = GetExternalAuthProviderForRequest(context.Request, externalAuthentication);
if (tenancySetting != null) {
context.Options.ClientId = tenancySetting.ClientId;
context.Options.ClientSecret = tenancySetting.ClientSecret;
}
context.RedirectUri = BuildChallengeUrl(context);
context.HttpContext.Response.Redirect(context.RedirectUri);
return Task.FromResult(0);
}
};
});
Update 2:
It is working now. I'm sure there must be a better way to do it, but I took the easy way out for now. I grabbed Microsoft's source code (https://github.com/aspnet/Security) and after looking through that I'm pretty sure that the HandleChallengeAsync method (Microsoft.AspNetCore.Authentication.OAuth.OAuthHandler) is being called in the pipeline prior to entering the RedirectToAuthorizationEndpoint event. HandleChallegeAsync takes care of building the RedirectUri property on the context. There doesn't seem to be an existing method to rebuild the RedirectUri in Microsoft's code so I copied out their code for BuildChallegeUrl and used that to rebuild the RedirectUri. I updated the sample code to reflect this change.
Try adding an OpenIdConnectEvents handler for the OnRedirectToIdentityProvider event and replace the ClientId and ClientSecret properties there, but I don't know if that will confuse Identity Server in some way. I don't have a similar use-case so I haven't tried this specific thing myself, but I intercept other events and change the protocol properties without any problems. I also don't know if you'd still need to set the id and secret on the options property itself, but that's easy enough to test. It would look something like this:
services.AddAuthentication()
.AddFacebook("Facebook", options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.ClientId = "abc";
context.ProtocolMessage.ClientSecret = "xyz";
return Task.FromResult<object>(null);
}
};
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
// you probably don't need these:
options.ClientId = oauth2config["FacebookId"];
options.ClientSecret = oauth2config["FacebookSecret"];
});
Obviously you'd need something more complex and implementation-specific to actually figure out the client etc.