Adding users to Slack channel - c#

In the code I have, I am doing a POST request to a Slack API to create a channel for me. According to Slack's documentation, I could add users into "user_ids." Debugging wise I called another API called channels.invite and adding users is successful, so I don't know what's wrong with adding users through conversations.create
class CreateChannels
{
private static readonly HttpClient client = new HttpClient();
static async Task CreateChannel()
{
var values = new Dictionary<string, string>
{
{ "token", "SLACK TOKEN" },
{ "name", "test" },
{ "is_private", "true" },
{ "user_ids", "USER-ID"}
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://slack.com/api/conversations.create", content);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
}
}
[1]: https://api.slack.com/methods/conversations.create

This does not work, because the parameter user_ids only works for so called workspace apps, but not for normal Slack like yours. You need to use conversations.invite.
From the documentation (emphasis mine):
Required for workspace apps. A list of between 1 and 30 human users
that will be added to the newly-created conversation. This argument
has no effect when used by classic Slack apps.

Related

Problem accessing passed parameter in url redirection

Hello I'm trying to do an Oauth 2.0 to integrate two systems together,
and it uses oauth so I have successfully been able to get the auth code from the url after authenticating, but I couldn't get the parameter from the redirect url to use it and generate the token within the code.
public void getData()
{
var query = "Store-url?scope=offline_access&state=12345678&response_type=code&approval_prompt=auto&redirect_uri=localhost/api/Omar/test&client_id=";
Response.Redirect(query);
}
this is the redirect url which take me to the store website to give the access and then it return the auth code in the url like this:
https://localhost/api/Omar/test?code=OPCKC8KwzcTPUWnvJsA-apz09-PsDNrDYTrUfffMIDU.pnjIZahbJBxC9G_BY4KEom2LNkPHKbnojJCeotylcKA&scope=settings.read+products.read_write+offline_access&state=12345678
and I tried the code to generate the token and it worked
the problem is how I get store the code passed in the parameter so I can use in my method that generates the token within the code
and the method code is
[HttpGet("test")]
public async Task<IActionResult> test(string code)
{
var values = new Dictionary<string, string>
{
{ "client_id", "" },
{ "client_secret", "" },
{ "grant_type", "authorization_code" },
{ "code", code },
{ "redirect_uri", "localhost/api/Omar/test" }
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("store-generate-token-url", content);
var responseString = await response.Content.ReadAsStringAsync();
return Ok("TEST SUCCESS"+ " "+ responseString);
}

Calling Microsoft Graph API to create an event, What am I doing wrong?

I've been trying to call Microsoft Graph API for creating events, however I've not been able to do it.
Context: I have a Web MVC application (C#) already in production, with the "common" authentication method, reading a database of users. Recently the customer asked me the possibility to create Microsoft Teams Meetings from the application and also those created meetings have to be scheduled in the Microsoft Teams Calendar with the "Join" button to enter the meeting.
I already configured the API permissions, client secret and used the other properties like tenant, user id, etc from the Azure Portal, I'm sharing a screenshot of my configuration. I'm doing the "Get access on behalf of a user" process.
API Permissions:
Permissions image
Taking the example of the authorize endpoint from the docs, of course I'm replacing the values with my own info
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=11111111-1111-1111-1111-111111111111
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=offline_access%20user.read%20mail.read
&state=12345
Here is my code to Receive the code once the user authorizes the permissions, I'm just storing the value in a static class for testing
public ActionResult ReceiveCode(string code)
{
AuthenticationConfig.Code = code;
//this.Code = code;
return RedirectToAction("Index");
}
Once I got the Auth code, I'm using it to create the event with the generated token, also I already verified that the token contains the permissions given in the Azure Portal.
This is the input for the /events endpoint
var json = JsonConvert.SerializeObject(new
{
subject = "Let's go for lunch",
body = new
{
contentType = "HTML",
content = "Does noon work for you?"
},
start = new
{
dateTime = "2017-04-15T12:00:00",
timeZone = "Pacific Standard Time",
},
end = new
{
dateTime = "2017-04-15T14:00:00",
timeZone = "Pacific Standard Time"
},
location = new
{
displayName = "Harry's Bar",
},
attendees = new List<Attendee>()
{
new Attendee
{
EmailAddress = new EmailAddress
{
Address = "mymail#whatever.com",
Name = "Foo Bar"
},
Type = AttendeeType.Required
}
},
allowNewTimeProposals = true,
isOnlineMeeting = true,
onlineMeetingProvider = "teamsForBusiness",
});
This is the complete method, for the json value, please see the json above. I also tried with the "me" url but it does not work either.
public async Task<ActionResult> OnlineMeeting()
{
try
{
var httpClient = new HttpClient();
var paramsDictionary = new Dictionary<string, string>();
paramsDictionary.Add("client_id",AuthenticationConfig.ClientId);
paramsDictionary.Add("scope", "Calendars.ReadWrite");
paramsDictionary.Add("code", AuthenticationConfig.Code);
paramsDictionary.Add("redirect_uri", "https://localhost:44379/Meeting/Reunion/ReceiveCode");
paramsDictionary.Add("grant_type", "authorization_code");
paramsDictionary.Add("client_secret", AuthenticationConfig.ClientSecret);
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/v2.0/token", "tenant");
var response = await httpClient.PostAsync(url, new FormUrlEncodedContent(paramsDictionary));
if (response.IsSuccessStatusCode)
{
var jsonResponse = await response.Content.ReadAsStringAsync();
var jsonResult = JsonConvert.DeserializeObject(jsonResponse) as JObject;
var accessToken = jsonResult.GetValue("access_token").ToString();
httpClient = new HttpClient();
var json = JsonConvert.SerializeObject(new { });
var defaultRequestHeaders = httpClient.DefaultRequestHeaders;
if (defaultRequestHeaders.Accept == null || !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json"))
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var data = new StringContent(json);
response = await httpClient.PostAsync("https://graph.microsoft.com/v1.0/users/{user id}/events", data);
if (response.IsSuccessStatusCode)
{
// Nice
}
else
{
Console.WriteLine($"Failed to call the web API: {response.StatusCode}");
string content = await response.Content.ReadAsStringAsync();
}
}
else
{
Console.WriteLine($"Failed to call the web API: {response.StatusCode}");
string content = await response.Content.ReadAsStringAsync();
}
}
catch (Exception ex)
{
}
return View();
}
I'm able to the get the token, but when trying to create the event returns the next response.
{
"error": {
"code": "ResourceNotFound",
"message": "Resource could not be discovered.",
"innerError": {
"date": "2021-08-31T22:58:18",
"request-id": "c5c07afa-fa89-4948-a9f8-f80ca4cbafc3",
"client-request-id": "c5c07afa-fa89-4948-a9f8-f80ca4cbafc3"
}
}
}
Am I missing something? Maybe the wrong endpoint?
Please, help.
Thanks in advance.

OAuth 2.0 Mailkit "Authentication failed" in MVC, but c# console-app works fine

Because Microsoft ends the support for Basic Authentication access for IMAP in Office 365 I try to update our application to use OAuth 2.0. We use MailKit in a MVC .Net web-application to access an IMAP mailbox, but I get an error saying Authentication failed. However, as a test, I can get it to work in a c# console-application.
The strange thing is:
If I copy the access-token I acquired using the console-application and use it in my web-application I can successfully authenticate and read emails. So that part works.
The authentication itself seems to be successful in the web-application. Our webapp redirects to the Microsoft login-page, MFA works, I see successful audits in Azure A/D and I do get a token in the callback. However, this token gives the Authentication failed error by Mailkit.
In Azure A/D I see some of these errors between the successful audits, but I'm not sure whether they are related or not: Error AADSTS16000 SelectUserAccount - This is an interrupt thrown by Azure AD, which results in UI that allows the user to select from among multiple valid SSO sessions. This error is fairly common and may be returned to the application if prompt=none is specified.
I already verified that the scope for which I acquire a token is the same for both console and web.
The main difference is that I use pca.AcquireTokenInteractive(scopes) in the console application to acquire the token, but I use a webclient call with a call-back in the MVC-controller.
Here is my code (MVC):
public ActionResult Index()
{
string clientID = "[client-id here]";
string clientSecret = "[client-secret here]";
string redirectUri = "[redirectUri here]";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize"),
TokenEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/token"),
ProtocolVersion = ProtocolVersion.V20,
};
List<string> scopes = new List<string>
{
"email",
"offline_access",
"https://outlook.office365.com/IMAP.AccessAsUser.All"
};
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scopes, new Uri(redirectUri));
return response.AsActionResultMvc5();
}
public async Task<ActionResult> Authorized(string code, string state, string session_state)
{
List<string> scopes = new List<string>
{
"IMAP.AccessAsUser.All",
"User.Read",
"offline_access"
};
HttpClient httpClient = new HttpClient();
var values = new Dictionary<string, string>
{
{ "Host", "https://login.microsoftonline.com" },
{ "Content-Type", "application/x-www-form-urlencoded" },
{ "client_id", "[client-id here]" },
{ "scope", string.Join(" ",scopes) },
{ "code", code },
{ "redirect_uri", [redirectUri here] },
{ "grant_type", "authorization_code" },
{ "client_secret", "[client-secret here]" },
{ "state", state },
};
var content = new FormUrlEncodedContent(values);
var response = await httpClient.PostAsync("https://login.microsoftonline.com/organizations/oauth2/v2.0/token", content);
var jsonString = await response.Content.ReadAsStringAsync();
var oathToken = JsonConvert.DeserializeObject<OathToken>(jsonString);
var oauth2 = new SaslMechanismOAuth2("[Email here]", oathToken.access_token);
var stringBuilder = new StringBuilder();
using (var client = new ImapClient())
{
try
{
await client.ConnectAsync("outlook.office365.com", 993, SecureSocketOptions.Auto);
await client.AuthenticateAsync(oauth2);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
for (int i = 0; i < inbox.Count; i++)
{
var message = inbox.GetMessage(i);
stringBuilder.AppendLine($"Subject: {message.Subject}");
}
await client.DisconnectAsync(true);
return Content(stringBuilder.ToString());
}
catch (Exception e)
{
return Content(e.Message);
}
}
}
The error Authentication failed occurs at the line
await client.AuthenticateAsync(oauth2);
The problem was the scope "email".
We had to remove that. Exactly why, I don't know. It was no problem when used in the console app. Maybe it had to do with the fact we used pca.AcquireTokenInteractive(scopes) in that.

Blazor Request blocked by CORS policy on PHP API

I am setting up a PHP API and a web-page based on client-side Blazor. But for some reason CORS is triggered and my login process or any requests to my PHP pages result in CORS errors.
I started out testing my PHP API with a C# console app and the Blazor app, I tried using without any database access to test the functionality. The Blazor is right now running with Preview 9. The PHP version is 5.3.8. I could in theory update it, but several other active projects are running on it and I do not have any test environment. MySQL version 5.5.24.
First I figured it might have been because I was running it on my local machine, so I pushed it to the website where the PHP and MySQL is also running. Still I run into this CORS error.
I am still just testing this, so I have tried setting it to allow any origin. I have not had any experience with CORS before this. Pretty sure I ought to be able to add PHP code in each file I access that should allow CORS, but since it should all be on the same website, I figure CORS should not even be relevant?
PHP Code:
function cors() {
// Allow from any origin
if (isset($_SERVER['HTTP_ORIGIN'])) {
// Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
// you want to allow, and if so:
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // cache for 1 day
}
// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
// may also be using PUT, PATCH, HEAD etc
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
exit(0);
}
echo "You have CORS!";
}
cors();
C# code using the injected HttpClient:
var resp = await Http.GetStringAsync(link);
The error I get is:
Access to fetch at 'https://titsam.dk/ntbusit/busitapi/requestLoginToken.php' from origin 'https://www.titsam.dk' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
The response I hoped to get was that the link I use return a token for the login as it does for my API.
Is it because its running client side maybe and this triggers CORS? But that does not seem to explain why I cannot make it allow all.
Update:
My C# code in OnInitializedAsync:
link = API_RequestLoginTokenEndPoint;
Http.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "basic:testuser:testpass");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, link);
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = "include"
};
var response = await Http.SendAsync(requestMessage);
var responseStatusCode = response.StatusCode;
var responseBody = await response.Content.ReadAsStringAsync();
output = responseBody + " " + responseStatusCode;
Update 2:
It finally works. The C# code I linked is the solution Agua From Mars suggested and it solved the problem to use SendAsync with a HttpRequestMessage and adding the Fetch property include credentials to it. Another alternative was to add this line to the startup:
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
Then I could keep doing what I did to begin with, using GetStringAsync as it becomes the default.
await Http.GetStringAsync(API_RequestLoginTokenEndPoint);
So all the solutions Agua From Mars suggested worked. But I encountered a browser problem, where it kept the CORS issue in the cache somehow even after it had gotten solved, so it seemed like nothing had changed. Some code changes would show a different result, but I guess the CORS part was kept alive. With Chrome it helped opening a new pane or window. In my Opera browser this was not enough, I had to close all panes with the site open to ensure it would clear the cache and then opening a new window or pane with the site works in Opera as well. I had already in both browsers trying to use ctrl-F5 and Shift-F5 to get them to clear the cache. This did not change anything.
I hope this will help others avoid spending 2-3 days on an issue like this.
update 3.1-preview3
In 3.1-preview3, we cannot use the fetch option per message, the options is global
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
WebAssemblyHttpMessageHandler has been removed. The HttpMessageHanlder used is WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler from WebAssembly.Net.Http but don't include WebAssembly.Net.Http in your depencies or the application will failled to launch.
If you want to use the HttpClientFactory you can implement like that :
public class CustomDelegationHandler : DelegatingHandler
{
private readonly IUserStore _userStore;
private readonly HttpMessageHandler _innerHanler;
private readonly MethodInfo _method;
public CustomDelegationHandler(IUserStore userStore, HttpMessageHandler innerHanler)
{
_userStore = userStore ?? throw new ArgumentNullException(nameof(userStore));
_innerHanler = innerHanler ?? throw new ArgumentNullException(nameof(innerHanler));
var type = innerHanler.GetType();
_method = type.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) ?? throw new InvalidOperationException("Cannot get SendAsync method");
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue(_userStore.AuthenticationScheme, _userStore.AccessToken);
return _method.Invoke(_innerHanler, new object[] { request, cancellationToken }) as Task<HttpResponseMessage>;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(p =>
{
var wasmHttpMessageHandlerType = Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
var constructor = wasmHttpMessageHandlerType.GetConstructor(Array.Empty<Type>());
return constructor.Invoke(Array.Empty<object>()) as HttpMessageHandler;
})
.AddTransient<CustomDelegationHandler>()
.AddHttpClient("MyApiHttpClientName")
.AddHttpMessageHandler<CustonDelegationHandler>();
}
3.0 -> 3.1-preview2
On Blazor client side your need to tell to the Fetch API to send credentials (cookies and authorization header).
It's describe in the Blazor doc Cross-origin resource sharing (CORS)
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = FetchCredentialsOption.Include
};
ex:
#using System.Net.Http
#using System.Net.Http.Headers
#inject HttpClient Http
#code {
private async Task PostRequest()
{
Http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "{OAUTH TOKEN}");
var requestMessage = new HttpRequestMessage()
{
Method = new HttpMethod("POST"),
RequestUri = new Uri("https://localhost:10000/api/TodoItems"),
Content =
new StringContent(
#"{""name"":""A New Todo Item"",""isComplete"":false}")
};
requestMessage.Content.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue(
"application/json");
requestMessage.Content.Headers.TryAddWithoutValidation(
"x-custom-header", "value");
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = FetchCredentialsOption.Include
};
var response = await Http.SendAsync(requestMessage);
var responseStatusCode = response.StatusCode;
var responseBody = await response.Content.ReadAsStringAsync();
}
}
You can set up this option globaly with WebAssemblyHttpMessageHandlerOptions.DefaultCredentials static proprerty.
Or you can implement a DelegatingHandler and set it up in DI with the HttpClientFactory:
public class CustomWebAssemblyHttpMessageHandler : WebAssemblyHttpMessageHandler
{
internal new Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken);
}
}
public class CustomDelegationHandler : DelegatingHandler
{
private readonly CustomWebAssemblyHttpMessageHandler _innerHandler;
public CustomDelegationHandler(CustomWebAssemblyHttpMessageHandler innerHandler)
{
_innerHandler = innerHandler ?? throw new ArgumentNullException(nameof(innerHandler));
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = "include"
};
return _innerHandler.SendAsync(request, cancellationToken);
}
}
In Setup.ConfigureServices
services.AddTransient<CustomWebAssemblyHttpMessageHandler>()
.AddTransient<WebAssemblyHttpMessageHandler>()
.AddTransient<CustomDelegationHandler>()
.AddHttpClient(httpClientName)
.AddHttpMessageHandler<CustomDelegationHandler>();
Then you can create an HttpClient for your API with IHttpClientFactory.CreateClient(httpClientName)
To use the IHttpClientFactory you need to install Microsoft.Extensions.Http package.
3.0-preview3 => 3.0-preview9
Replace WebAssemblyHttpMessageHandler with BlazorHttpMessageHandler

Browse Javascript generated web pages

There is this online form (https://servizi.ivass.it/RuirPubblica/) where you can make a search (just make a blank search). For each result it gives, I need to click on the result and export the list that is in the 5th table of the details page.
So basically I want to make a software that does that for me:
Submit a search with my own criteria
Access each page of the result items
Access each item detail page
Obtain the rows in the 5th tag so that I can append them to a list
Using Fiddler I checked which parameters where used in the POST request when I clicked the "Search" button, and tried to do the same with .Net.
If I try to access the base address with HttpClient it returns the correct HTML of the search form, but when I submit the following POST request with search parameters I get a web page showing the error "Warning: Session Expired".
This happens also if I make the search POST call alone, without first accessing the home page, so I'm not sure it is related to keeping the session alibe between two requests.
public MainWindow()
{
InitializeComponent();
var cookieJar = new CookieContainer();
var handler = new HttpClientHandler
{
CookieContainer = cookieJar,
UseCookies = true,
UseDefaultCredentials = false
};
client = new HttpClient(handler)
{
BaseAddress = new Uri("https://servizi.ivass.it/RuirPubblica/Search.faces")
};
}
private async Task TryHttp()
{
// Access the search page
var response = await client.GetAsync(client.BaseAddress);
var responseString = await response.Content.ReadAsStringAsync();
// Perform the search
var values = new Dictionary<string, string>
{
{ "FormSearch", "FormSearch" },
{ "FormSearch:j_id_jsp_558348152_13", "PG" },
{ "FormSearch:j_id_jsp_558348152_16", "custom" },
{ "FormSearch:SecE", "on" },
{ "FormSearch:matricola", "" },
{ "FormSearch:ragioneSociale", "" },
{ "FormSearch:provincia", "NA" },
{ "FormSearch:SearchButton", "Ricerca" },
{ "javax.faces.ViewState", "j_id1:j_id5" },
};
var content = new FormUrlEncodedContent(values);
response = await client.PostAsync(client.BaseAddress, content);
// Here I'm getting a web page showing the error "Warning: Session expired"
responseString = await response.Content.ReadAsStringAsync();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
TryHttp();
}
If you can define it, it can be done. As you will understand from the comments StackOverflow is all about programming questions, so I will only help you with that.
In principal if the web page is "parsable" as HTML and communicates using HTTP you can do anything with it that a normal web browser would do. The website you reference does initially appear to do anything out of the ordinary.
HTMLAgilityPack can be very useful for parsing the DOM and navigating and extracting the contents.
To make HTTP requests with C# you should use the HttpClient class.
There are older options like the HttpWebClient, there is good answer here on SO to help you decide between the two.
For quick reference, Fiddler is available here, I too have used it many times and would recommended it, although it can cause problems with HTTPS traffic and debugging.

Categories

Resources