VSTS API - Accessing team rooms throws 401 error - visual-studio

I'm trying to access the Team rooms functionality in Visual Studio Team Services (VSTS)
With a valid token if I make a request to :
https://.visualstudio.com/DefaultCollection/_apis/chat/rooms?api-version=1.0
I get an error 401. I have tested the token and I can make requests to other endpoints without an issue.
status code: 401, headers {
"Content-Length" = 10828;
"Content-Type" = "text/html; charset=utf-8";
Date = "Fri, 17 Feb 2017 09:36:17 GMT";
Server = "Microsoft-IIS/10.0";
"Strict-Transport-Security" = "max-age=31536000; includeSubDomains";
"Www-Authenticate" = Bearer;
p3p = "CP=\"CAO DSP COR ADMa DEV CONo TELo CUR PSA PSD TAI IVDo OUR SAMi BUS DEM NAV STA UNI COM INT PHY ONL FIN PUR LOC CNT\"";
"x-content-type-options" = nosniff;
"x-frame-options" = SAMEORIGIN;
"x-powered-by" = "ASP.NET";
"x-tfs-processid" = "925585e2-35c8-4691-b646-c96e48f04ee0";
"x-tfs-serviceerror" = "TF400813%3a+Resource+not+available+for+anonymous+access.+Client+authentication+required.";
"x-tfs-soapexception" = "%3c%3fxml+version%3d%221.0%22+encoding%3d%22utf-8%22%3f%3e%3csoap%3aEnvelope+xmlns%3asoap%3d%22http%3a%2f%2fwww.w3.org%2f2003%2f05%2fsoap-envelope%22%3e%3csoap%3aBody%3e%3csoap%3aFault%3e%3csoap%3aCode%3e%3csoap%3aValue%3esoap%3aReceiver%3c%2fsoap%3aValue%3e%3csoap%3aSubcode%3e%3csoap%3aValue%3eInvalidIdentityException%3c%2fsoap%3aValue%3e%3c%2fsoap%3aSubcode%3e%3c%2fsoap%3aCode%3e%3csoap%3aReason%3e%3csoap%3aText+xml%3alang%3d%22en%22%3eTF400813%3a+Resource+not+available+for+anonymous+access.+Client+authentication+required.%3c%2fsoap%3aText%3e%3c%2fsoap%3aReason%3e%3c%2fsoap%3aFault%3e%3c%2fsoap%3aBody%3e%3c%2fsoap%3aEnvelope%3e";
"x-vss-s2stargetservice" = "00000002-0000-8888-8000-000000000000/visualstudio.com";
} })

You need to grant application the Team rooms (read and write) permission.
So, check the application you registered whether has the Team rooms (read and write) scope. Also check the web application config file (web.config) whether there is vso.chat_write in Scope (key) value.
For example:
<add key="Scope" value="vso.agentpools vso.build vso.chat_write vso.code vso.dashboards vso.identity vso.packaging vso.release vso.test vso.work"/>

Related

Application Windows Form strange behavior on another computer with same .NET framework 4.8.1 installed

I have created an application with .net Framework 4.8.1, it works well on the pc where it was developed (computer under windows 11) but has a different behavior on two other computers under windows 10 freshly installed and updated.
Part I = The context =
My application sends an http request with http2 to retrieve a token from a remote CloudFlare server.
Here is the request I send to the server:
{Method: POST, RequestUri: 'https://this.is.confidential/json/gov/v10/Api/CreateApiKey', Version: 2.0, Content: System.Net.Http.StringContent, Headers:
{
Content-Type: text/plain; charset=utf-8
}}
On the computer running Windows 11, the server response is as follows:
{StatusCode: 200, ReasonPhrase: '', Version: 2.0, Content: System.Net.Http.NoWriteNoSeekStreamContent, Headers:
{
Cache-Control: no-store, no-cache
Date: Tue, 13 Dec 2022 19:37:43 GMT
Server: cloudflare
Set-Cookie: __cf_bm=W5wDtxsixycI1_5G5fdgdfJGkVCX5nT9tzG8-1670960263-0-AfqDRz1MMF67L/ntDOShg3Jz3GZxMo/UtCYReTfDIJl1g0vQ/MfeVa5C8/PxQLtucWoYvV5nYGEyyuN/r19aWWo=; path=/; expires=Tue, 13-Dec-22 20:07:43 GMT; domain=.thisisconfidential.com; HttpOnly; Secure; SameSite=None
Vary: Accept-Encoding
access-control-allow-origin: *
x-duration: 33.200026
cf-cache-status: DYNAMIC
cf-ray: 779127ecadedf11c-CDG
Content-Type: application/json
}}
On the computer running Windows 10, the server response is as follows:
{StatusCode: 403, ReasonPhrase: '', Version: 2.0, Content: System.Net.Http.NoWriteNoSeekStreamContent, Headers:
{
Cache-Control: no-store, must-revalidate, no-cache, max-age=0, private, post-check=0, pre-check=0
Date: Tue, 13 Dec 2022 13:18:28 GMT
Server: cloudflare
Set-Cookie: __cf_bm=oKXtpXuiUmH5MeEA8n67RTG33dfZhv5Th5c3ZX0PAUSw1oA-1670937508-0-AX+aH9+MYxwNqOdbMZCmhg35ByjwgaTmtJdditJ7K1xEU1ex7au/PyiJ4JbCImQvICHQPcr+QJijZSeYfKme3/o=; path=/; expires=Tue, 13-Dec-22 13:48:28 GMT; domain=.thisisconfidential.com; HttpOnly; Secure; SameSite=None
x-frame-options: SAMEORIGIN
referrer-policy: same-origin
cf-ray: 778efc668ed6d07b-CDG
Content-Length: 16
Content-Type: text/plain; charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:01 GMT
}}
Part II = The code =
To use http2 in .NET 4.8.1 i use a HttpHandler custom class:
Imports System.Net.Http
Public Class Http2CustomHandler : Inherits WinHttpHandler
Protected Overrides Function SendAsync(request As HttpRequestMessage, cancellationToken As Threading.CancellationToken) As Task(Of HttpResponseMessage)
request.Version = New Version("2.0")
Return MyBase.SendAsync(request, cancellationToken)
End Function
End Class
And I call it when I send the request:
Using wclient As New HttpClient(New Http2CustomHandler())
Dim url As String = "https://this.is.confidential.com/json/gov/v10/Api/CreateApiKey"
With wclient
.BaseAddress = New Uri(url)
.DefaultRequestHeaders.UserAgent.Add(New Headers.ProductInfoHeaderValue("govAgent", "3.6.15"))
.DefaultRequestHeaders.Accept.Clear()
.DefaultRequestHeaders.Accept.Add(New Headers.MediaTypeWithQualityHeaderValue("text/plain"))
End With
Dim req = New HttpRequestMessage With {
.RequestUri = New Uri("https://this.is.confidential.com/json/gov/v10/Api/CreateApiKey"),
.Method = HttpMethod.Post,
.Content = New StringContent($"login={login}&password={password}&gov={gov}&long_life_token=true", Encoding.UTF8, "text/plain")
}
Dim response As HttpResponseMessage = Await wclient.SendAsync(req)
Dim jsonString = response.Content.ReadAsStringAsync().Result
Part III = some informations =
To launch my application on Windows 10 computers I copied the Release folder containing all the useful DLLs. I also published my code and installed my application on computers running Windows 10.
I also moved my VB net project on the other computers to test to compile and run and I have the same problem, the request fails systematically.
My windows 11 and 10 are up to date and have all .NET 4.8.1 installed.
I'm going crazy, I don't understand what the problem is.
Thank you.
UPDATE :
There is actually a bug on windows 10 and .net framework 4.8/4.8.1 combined with WinHttpHandler. It does not work with 2.0 http version BUT if you put 3.0 http version it works with cloudflare WAF !! That's stupid !
The following is not an answer but may be helpful.
Configure tracing:
If your application doesn't have a .config file (ex: app.config, Web.config), then add one. In the .config file, add the following to <configuration>...</configuration>. See How to: Configure network tracing for more information.
<system.diagnostics>
<sources>
<source name="System.Net">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.Cache">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.Http">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.Sockets">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.WebSockets">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
</sources>
<switches>
<add name="System.Net" value="Verbose"/>
<add name="System.Net.Cache" value="Verbose"/>
<add name="System.Net.Http" value="Verbose"/>
<add name="System.Net.Sockets" value="Verbose"/>
<add name="System.Net.WebSockets" value="Verbose"/>
</switches>
<sharedListeners>
<add name="System.Net"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="network.log"
traceOutputOptions="ProcessId, DateTime"
/>
</sharedListeners>
<trace autoflush="true"/>
</system.diagnostics>
Note: initializeData specifies the log file name. To specify a fully-qualified log file name, escape each of the backslashes by adding an extra one initializeData="C:\\Temp\\network.log"
Add the following Imports:
Imports System.Net
Imports System.Net.Http
Imports System.Text
Imports System.Reflection
Imports System.Security.Authentication
GetTLSVersion:
Note: The following code is converted to VB.NET from this post.
Public Async Function GetTLSVersion(url As String) As Task(Of String)
Using client As HttpClient = New HttpClient()
Using response As HttpResponseMessage = Await client.GetAsync(url)
If TypeOf (response.Content) Is StreamContent Then
Dim webExceptionWrapperStream = GetField(response.Content, "content")
Dim connectStream = GetBaseField(webExceptionWrapperStream, "innerStream")
Dim connection = GetProperty(connectStream, "Connection")
Dim tlsStream = GetProperty(connection, "NetworkStream")
Dim state = GetField(tlsStream, "m_Worker")
Dim protocol As SslProtocols = CType(GetProperty(state, "SslProtocol"), SslProtocols)
Return protocol.ToString()
End If
End Using
Return String.Empty
End Using
End Function
Private Function GetBaseField(obj As Object, field As String) As Object
Return obj?.GetType()?.BaseType?.GetField(field, BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public)?.GetValue(obj)
End Function
Private Function GetField(obj As Object, field As String) As Object
Return obj?.GetType()?.GetField(field, BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public)?.GetValue(obj)
End Function
Private Function GetProperty(obj As Object, prop As String) As Object
Return obj?.GetType()?.GetProperty(prop, BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public)?.GetValue(obj)
End Function
Usage
Private Async Sub btnRun_Click(sender As Object, e As EventArgs) Handles btnRun.Click
Dim tlsVersion As String = Await GetTLSVersion("https://www.bing.com")
Debug.WriteLine($"TLS version: {tlsVersion}{Environment.NewLine}")
End Sub
Update:
It seems that TLS 1.3 can be enabled in Windows 10.
Create a full backup of your computer (recommended)
Create a restore point
Open Control Panel
Select View by: Small icons
Click System
On left side, click System protection
If protection isn't turned on for the C:, then click Configure to turn it on. Select desired size and click OK.
Click Create to create a restore point
Enter desired name for restore point
Click Create
Copy the code/text below to a file that ends in .reg (ex: EnableTls13.reg).
Open you're favorite text editor (ex: Notepad)
Copy the code/text below
Save file with a ".reg" extension (ex: EnableTls13.reg). Alternatively, save with a ".txt" extension. Then right-click the file and rename it so that it has a ".reg" extension.
Win 10 (64-bit):
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3]
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client]
"DisabledByDefault"=dword:00000000
"Enabled"=dword:00000001
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server]
"DisabledByDefault"=dword:00000000
"Enabled"=dword:00000001
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\HTTP\Parameters]
"EnableHttp3"=dword:00000001
Then double click the file to add the entries to the registry. Alternatively, add each of the registry entries to the registry using regedit.
Reboot the computer.
Resources:
Detecting TLS Version used for HttpClient POST or GET calls
How to: Configure network tracing
How to Enable TLS 1.2 and TLS 1.3 on Windows Server
How to enable or disable TLS 1.3 in Windows 10?

Plaid Account ID returns INVALID_ACCOUNT_ID when used as Options in requests in Development

When my application runs in the Development environment, the Account ID that Plaid returns will result in an "INVALID_ACCOUNT_ID" error when applied in an Options object that is sent along with the request. The example code is a simple flow that I expect my application to support. It loads in Accounts from Plaid based on the access token and captures the account IDs, and then randomly chooses an account to query Plaid for the current balance.
import os
import random
from typing import Any
import plaid
from plaid.api import plaid_api
from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest
from plaid.model.accounts_get_request import AccountsGetRequest
from plaid.model.accounts_get_request_options import AccountsGetRequestOptions
# Load in from environ
plaid_env = os.environ.get("PLAID_ENV", "sandbox").title()
client_id = os.environ["PLAID_CLIENT_ID"]
secret = os.environ["PLAID_SECRET"]
access_token = os.environ["ACCESS_TOKEN"]
# Initialize Plaid Client
configuration = plaid.Configuration(
host=getattr(plaid.Environment, plaid_env),
api_key={
"clientId": client_id,
"secret": secret,
},
)
api_client = plaid.ApiClient(configuration)
plaid_client = plaid_api.PlaidApi(api_client)
# Request for all account balances
account_balance_request = AccountsBalanceGetRequest(access_token=access_token)
account_balance_response = plaid_client.accounts_balance_get(account_balance_request)
# Data structure for accounts
accounts = [
{
"account_name": a["official_name"],
"account_id": a["account_id"],
}
for a in account_balance_response["accounts"]
]
# Pick one account to operate on
test_account = random.choice(accounts)
# Request for Account Balance this account's balance
try:
test_account_balance_request = AccountsGetRequest(
access_token=access_token,
options=AccountsGetRequestOptions(account_ids=[test_account["account_id"]]),
)
test_account_balance_response = plaid_client.accounts_get(
test_account_balance_request
)
print(
f"Balance of {test_account['account_name']} is "
f"{test_account_balance_response['accounts'][0]['balances']['current']}"
)
except Exception as e:
print(f"Unable to update balance of account. Error = {e}")
When operating in the Sandbox, this works perfectly fine:
Balance of Plaid Gold Standard 0% Interest Checking is 110.0
When operating in the Development environment, it returns:
Unable to update balance of account. Error = (400)
Reason: Bad Request
HTTP response headers: HTTPHeaderDict({'Server': 'nginx', 'Date': 'Wed, 05 Oct 2022 02:58:30 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '308', 'Connection': 'keep-alive', 'plaid-version': '2020-09-14'})
HTTP response body: {
"display_message": null,
"documentation_url": "https://plaid.com/docs/?ref=error#invalid-input-errors",
"error_code": "INVALID_ACCOUNT_ID",
"error_message": "one or more of the account IDs is invalid",
"error_type": "INVALID_INPUT",
"request_id": "",
"suggested_action": null
}
While this sample code is redundant, I am experiencing the INVALID_ACCOUNT_ID error on any function that supports account_ids in the options. If I remove the Options objects from offending requests in my application, the requests works. I have reviewed the causes that are cited in the documentation and I'm not convinced any of them relate to this issue. Would anyone be able to help?

How to obtain a client secret in a teams toolkit project?

I'm working on a tab app where I intend using an on-behalf-of flow to obtain access token from azure active directory, so as to request data from Microsoft graph endpoints, and implementing this requires a client secret.
Is there a way I can get the client secret in a teams toolkit project just like I can get an application ID?
(Update) Details of what I'm trying to do
I'm working on an app where I would be calling Microsoft graph endpoints (protected by azure ad) to get data. The challenge I'm facing currently is how to handle authentication in a Teams tab app project created using Microsoft Teams Toolkit, so as to obtain an access token to request data from the graph endpoints or create an authenticated graph client.
What I have tried:
I have tried the code below, using the teamsfx.login() function within the react component where I'm calling a protected graph endpoint. But whenever I click the button to initiate a graph call, there is always a pop-up flash.
export const GraphEmail: React.FC = () => {
const [messages, setMessages] = useState<any[]>([]);
const handleGetMyMessagesOnClick = async (event: any): Promise<void> => {
await getMessages();
};
const getMessages = async (promptConsent: boolean = false): Promise<void> => {
const teamsfx = new TeamsFx();
await teamsfx.login(["User.Read", "Mail.Read"]);
const graphClient = createMicrosoftGraphClient(teamsfx, ["User.Read", "Mail.Read"]);
await graphClient
.api("/me/messages")
.select(["receivedDateTime", "subject"])
.top(15)
.get(async (error: any, rawMessages: any, rawResponse?: any) => {
if (!error) {
setMessages(rawMessages.value);
Promise.resolve();
} else {
console.error("graph error", error);
}
});
};
return (
<Flex column gap="gap.small">
<Header>Recent messages in current user&apos;s mailbox</Header>
<Button primary
content="Get My Messages"
onClick={handleGetMyMessagesOnClick}></Button>
<List selectable>
{
messages.map((message, i) => (
<List.Item key={i} media={<EmailIcon></EmailIcon>}
header={message.receivedDateTime}
content={message.subject} index={i}>
</List.Item>
))
}
</List>
</Flex>
);
}
In order to remove the consistent flash after the first popup for the actual login, since the user is already logged-in during the first button click, I made the changes below (idea gotten from the useGraph() component code on GitHub). But then I got an "uncaught (in promise) undefined" error when the button is clicked. The console logs are displayed below too.
export const NewGraphEmail: React.FC = () => {
const [needConsent, setNeedConsent] = useState(false);
const [messages, setMessages] = useState<any[]>([]);
const handleGetMyMessagesOnClick = async (event: any): Promise<void> => {
await getMessages();
};
const getMessages = async (promptConsent: boolean = false): Promise<void> => {
const teamsfx = new TeamsFx();
const scope = ["User.Read", "Mail.Read"];
if (needConsent) {
try {
await teamsfx.login(scope);
setNeedConsent(false);
// Important: tokens are stored in sessionStorage, read more here: https://aka.ms/teamsfx-session-storage-notice
} catch (err: unknown) {
if (err instanceof ErrorWithCode && err.message?.includes("CancelledByUser")) {
const helpLink = "https://aka.ms/teamsfx-auth-code-flow";
err.message +=
'\nIf you see "AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application" ' +
"in the popup window, you may be using unmatched version for TeamsFx SDK (version >= 0.5.0) and Teams Toolkit (version < 3.3.0) or " +
`cli (version < 0.11.0). Please refer to the help link for how to fix the issue: ${helpLink}`;
}
throw err;
}
}
const graphClient = createMicrosoftGraphClient(teamsfx, scope);
await graphClient
.api("/me/messages")
.select(["receivedDateTime", "subject"])
.top(15)
.get(async (error: any, rawMessages: any, rawResponse?: any) => {
if (!error) {
setMessages(rawMessages.value);
Promise.resolve();
} else if (error instanceof GraphError && error.code?.includes("UiRequiredError")) {
// Silently fail for user didn't consent error
setNeedConsent(true);
// getMessages();
} else {
console.log("graph error", error);
}
});
};
return (
<Flex column gap="gap.small">
<Header>Recent messages in current user&apos;s mailbox</Header>
<Button primary
content="Get My Messages"
onClick={handleGetMyMessagesOnClick}></Button>
<List selectable>
{
messages.map((message, i) => (
<List.Item key={i} media={<EmailIcon></EmailIcon>}
header={message.receivedDateTime}
content={message.subject} index={i}>
</List.Item>
))
}
</List>
</Flex>
);
}
The logs in the browser console
[Tue, 09 Aug 2022 10:22:00 GMT] : #microsoft/teamsfx : Info - Create
Microsoft Graph Client useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:00 GMT] : #microsoft/teamsfx : Info - Create
Microsoft Graph Authentication Provider with scopes: 'User.Read Mail.Read'
useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:00 GMT] : #microsoft/teamsfx : Info - Get Graph
Access token with scopes: 'User.Read Mail.Read'
useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:00 GMT] : #microsoft/teamsfx : Info - Create teams
user credential useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:00 GMT] : #microsoft/teamsfx : Verbose - Validate
authentication configuration
useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:00 GMT] : #microsoft/teamsfx : Info - Get access
token with scopes: User.Read Mail.Read
useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:00 GMT] : #microsoft/teamsfx : Verbose - Get SSO
token from memory cache
useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:00 GMT] : #microsoft/teamsfx : Verbose - Failed to
call acquireTokenSilent. Reason: no_account_error: No account object
provided to acquireTokenSilent and no active account has been set. Please
call setActiveAccount or provide an account on the request..
authorize:74 BSSO Telemetry:
{"result":"Error","error":"NoExtension","type":"ChromeSsoTelemetry","data":
{},"traces":["BrowserSSO Initialized","Creating ChromeBrowserCore
provider","Sending message for method CreateProviderAsync","Received message
for method CreateProviderAsync","Error: ChromeBrowserCore error NoExtension:
Extension is not installed."]}
DevTools failed to load source map: Could not load content for
https://login.microsoftonline.com/5d2e66da-54ba-4897-82ee-
60eeb8ce5994/oauth2/v2.0/4616d84a89b332161726.map: HTTP error: status code
404, net::ERR_HTTP_RESPONSE_CODE_FAILURE
useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:02 GMT] : #microsoft/teamsfx : Verbose - Failed to
call ssoSilent. Reason: login_required: AADSTS50058: A silent sign-in
request was sent but no user is signed in.
Trace ID: 5043daaa-b142-4083-9ad9-a798c2303b00
Correlation ID: ce16ec27-0261-423e-96f6-810344f76647
Timestamp: 2022-08-09 10:22:03Z.
useTeamsFx.js:34
[Tue, 09 Aug 2022 10:22:02 GMT] : #microsoft/teamsfx : Error - Failed to get
access token cache silently, please login first: you need login first before
get access token.
TestGraph.tsx:16
Uncaught (in promise) undefined
The uncaught error points to the end of the "handleGetMyMessagesOnClick" function above.
The other options:
The useGraph() hook: I would have loved to use the hook directly, but it seem to be suited for cases when using microsoft graph toolkit components, which won't serve my purpose during the project.
The on-behalf-of flow: I believe this would have solved the problem, following the steps in this video from the Microsoft 365 Developer channel, but the solution requires having an azure ad app client secret, which I don't know how to get in a microsoft teams toolkit project, since microsoft teams toolkit handles azure ad app registration.
A Client secret is a password to optain an access token though an API.
You need to implement an API than kan exchange an Teams SSO token, for a MS Graph API access token using a client secret (on-behalf-of). This client secret must never be exposed to the user/client, and should be secret; hence the name.
See this for a detailed explaination.
What you're wanting in this case is an "on behalf of" token from Graph, which lets you make calls to graph from your app as if it was the user ("on behalf of" the user) and it seems reasonable enough at first to do this in your client-side code. However, it turns out this isn't actually secure because it means the user's token is flying around almost in the open. As a result, it's better to create your own backend API (e.g. in an Azure Function) and make the "on behalf of" ("OBO") call from within there. Teams Toolkit actually creates some structure to help with this backend API, I think.
I'm not sure how well it covers Teams Toolkit (it's a while since I last watched it), but this video is an excellent overview: https://www.youtube.com/watch?v=kruUnaZgQaY . See here also for info: https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/visual-studio-code-tab-sso

Xamarin Android : Https Post Request to Local Intranet Web Api Causes error : StatusCode: 404, ReasonPhrase: 'Not Found'

This is going to be a long question..
Our company has to follow PCI Standards, so a while back we had to ensure all our Servers were TLS1.2 compliant. As a result we implemented TLS as explained here in our Xamarin Forms app. But we noticed issues in Android versions less then Api 22. So we implemented a dependency service for fetching the HTTPClient and if the Api versions were less than 22 we implemented a custom ssl socket factory, here's the example.
Everything was fine till a few weeks back there was a decision to upgrade the servers to Windows 2016 on the dev environment. We've redeployed our Web Api to the server and ever since then, the api is inaccessible from a few devices. The problem we've faced is in Samsung Galaxy S4(Android 4.4) and Nexus 5(Android 5.1.1). We've tried testing the app on a Samsung Galaxy A7(Android 6) and it works okay. iOS is also fine.
This is the error we recieve on the S4 and Nexus 5
StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content:
System.Net.Http.StreamContent, Headers: { Date: Wed, 20 Sep 2017
04:00:09 GMT Server: Microsoft-IIS/10.0 X-Android-Received-Millis:
1505880010792 X-Android-Response-Source: NETWORK 404
X-Android-Selected-Transport: http/1.1 X-Android-Sent-Millis:
1505880010781 X-Powered-By: ASP.NET Content-Length: 1245 Content-Type:
text/html
Here's the signature of the Web Api
[HttpPost("GetMinimumVersion")]
public GetMinimumVersionResponse GetMinimumVersion([FromBody] GetMinimumVersionRequest value)
And this is the code we use to make a post request
using (_httpclient = _deviceInfo.GetHttpClient())
{
_httpclient.MaxResponseContentBufferSize = 256000;
_httpclient.BaseAddress = new Uri(BaseAddress);
_httpclient.Timeout = timeout > 0 ? TimeSpan.FromSeconds(timeout) : TimeSpan.FromSeconds(60000);
Insights.Track("ApiUrlCalled", new Dictionary<string, string> { { "Api URL", url } });
var jsonOut = new StringContent(JsonConvert.SerializeObject(body, new IsoDateTimeConverter()));
jsonOut.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await _httpclient.PostAsync(url, jsonOut);
switch (response.StatusCode)
{
case HttpStatusCode.OK:
var content = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<T>(content);
ReceiveNotificationDateTime(result);
return result;
default:
var result1 = new T { StatusID = (int)SystemStatusOutcomes.Failed, StatusMessage = response.ToString() };
ReceiveNotificationDateTime(result1);
return result1;
}
}
It's worth noting that the app when talking to the production api works fine on all devices. And we're also able to make post requests to the dev api via Postman.
After some digging and scratching, I found out that the ciphers used on production and dev were different.
Here's the cipher used on Prod
and here's the one used on dev.
I had a look at the SSL Ciphers Android supports here. And it looks like the ciper suite TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 is supported in Android Api version 20+. This makes sense that it wont work on Android 4.4. But why would we get this error on Nexus 5? Any pointers?
Also is there any workaround to get this cipher enabled on Android 4.4?

html5 cache-manifest file in mvc3 and windows azure returns 500 error

i'm hosting an mvc3 web application in a windows azure web-role , and have recently added the html5 cash manifest.
in local environment everything works well , but once uploaded to dev environment on azure , i'm getting an HTTP 500 error when trying to access the manifest.
the cache manifest file is being served by an action and controller , similar to the technique sescribed in Dean Hume's article
the controller:
public ActionResult Manifest()
{
Response.ContentType = "text/cache-manifest";
Response.ContentEncoding = System.Text.Encoding.UTF8;
Response.Cache.SetCacheability( System.Web.HttpCacheability.NoCache);
return View();
}
the View:
#{
Response.ContentType = "text/cache-manifest";
Response.ContentEncoding = System.Text.Encoding.UTF8;
Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
Layout = null;
}
CACHE MANIFEST
# 29/3/2012:V6
NETWORK:
*
CACHE:
#JS FILES
/Scripts/rolllo_1.0.js
/Scripts/rolllo_1.0.js
/Scripts/jquery.mobile-1.0b3.min.js
/Scripts/jquery.validate.min.js
/Scripts/jquery.validate.unobtrusive.min.js
/Scripts/jquery.unobtrusive-ajax.min.js
/Scripts/rolllo_1.0.js
#CSS FILES
/Content/Rtl.css
/Content/JQM/jquery.mobile-1.0b3.css
/Content/Site.css?v=2"
FALLBACK:
/Group/Offline
the _Layout:
<html manifest="#Url.Action("Manifest","Home")">
error messages:
from the chrome console : 'Application Cache Error event: Manifest fetch failed (500)'
and from fiddler :
HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/cache-manifest; charset=utf-8
Expires: -1
Date: Thu, 29 Mar 2012 09:32:22 GMT
Content-Length: 5875
i'd love some help.
The problem is probably that IIS is missing the MIME type in Azure. Take a look at:
http://blog.christian-heindel.de/2011/10/23/how-to-host-html5-offline-web-applications-on-an-iis-web-server/
But keep in mind for Azure you will have to do this in a startup task or in the WebRole OnStart method so it will happen anything your instance starts up.

Resources