Unable to consume TFS 2015 API. Getting 401 unauthrozed error - azure-devops

I tried TFS 2015 REST API Authentication
However, it mentions request object (as I can't use javascript), not sure where is the request object or what type of it.
I am trying to pass query id and the code should execute the query and get result via API.
The solution works from my local, however, after publishing to server it does not seems working.
I also checked that the TFS is accessible from server using the credentials.
My code below:
private HttpClientHandler GetTfsCredentials()
{
HttpClientHandler handler2 = new HttpClientHandler { UseDefaultCredentials = true };
handler2.Credentials = new NetworkCredential("username", "password", "domain");
return handler2;
}
private async Task<object> GetQueryResults(string queryId)
{
string tfsApiUrl = ConfigurationManager.AppSettings["TfsApiUrl"];
string tfsProjectName = ConfigurationManager.AppSettings["TfsProjectName"];
string TfsProjectGuid = ConfigurationManager.AppSettings["TfsProjectGuid"];
//I tried both credentials and credentials2, but none of them working
string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"{""}:{"password"}"));
string credentials2 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("domain\\username:password") );
if (!string.IsNullOrEmpty(tfsApiUrl) && !string.IsNullOrEmpty(tfsProjectName)
&& !string.IsNullOrEmpty(Id))
{
log.Info("GetQueryResults:: Config values found");
using (var client = new HttpClient(GetTfsCredentials()) { BaseAddress = new Uri(tfsApiUrl) })
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials2);
HttpResponseMessage response = client.GetAsync($"{tfsProjectName}/_apis/wit/wiql/{Id}").Result;
log.Info("GetQueryResults:: response.ReasonPhrase" + response.ReasonPhrase.ToString());
log.Info("GetQueryResults:: response" + response.ToString());
log.Info("GetQueryResults:: response.IsSuccessStatusCode" + response.IsSuccessStatusCode.ToString());
string workItemList = null;
if (response.IsSuccessStatusCode)
{
//do something
}
}
}
return null;
}
The error I received is:
2020-03-20 16:17:35,382 INFO GetQueryResults:: response.ReasonPhrase Unauthorized
2020-03-20 16:17:35,382 INFO GetQueryResults:: responseStatus Code: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
X-TFS-ProcessId: 115b5bba-0bf4-45e2-a3b2-2913ccc93f09
ActivityId: bb21d947-99a3-44dc-bdb7-317d7af34934
X-TFS-Session: bb21d947-99a3-44dc-bdb7-317d7af34934
X-VSS-E2EID: bb21d947-99a3-44dc-bdb7-317d7af34934
X-FRAME-OPTIONS: SAMEORIGIN
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%3eUnauthorizedRequestException%3c%2fsoap%3aValue%3e%3c%2fsoap%3aSubcode%3e%3c%2fsoap%3aCode%3e%3csoap%3aReason%3e%3csoap%3aText+xml%3alang%3d%22en%22%3eTF400813%3a+The+user+%27CWOPA%5cSTCTCAPD006%24%27+is+not+authorized+to+access+this+resource.%3c%2fsoap%3aText%3e%3c%2fsoap%3aReason%3e%3c%2fsoap%3aFault%3e%3c%2fsoap%3aBody%3e%3c%2fsoap%3aEnvelope%3e
X-TFS-ServiceError: TF400813%3a+The+user+%27CWOPA%5cSTCTCAPD006%24%27+is+not+authorized+to+access+this+resource.
Server: Microsoft-IIS/8.5
WWW-Authenticate: Bearer
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
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"
Lfs-Authenticate: NTLM
X-Content-Type-Options: nosniff
Date: Fri, 20 Mar 2020 20:17:35 GMT
Content-Length: 82
Content-Type: text/plain; charset=utf-8
}
2020-03-20 16:17:35,382 INFO GetQueryResults:: response.IsSuccessStatusCode False

It looks like you are doing authentication in two different ways at once:
In the GetTfsCredentials-Method you set up Windows Authentication (NTLM or Kerberos)
By adding client.DefaultRequestHeaders.Authorization your try to set up Basic Authentication
Your TFS indicates (see WWW-Authenticate Header) that it supports Bearer, Negotiate and NTLM; but not Basic.
I would try:
Remove client.DefaultRequestHeaders.Authorization, credentials and credentials2. This should remove Basic-Authentication
Remove UseDefaultCredentials = true since you set explicit credentials the next line. UseDefaultCredentials tells HttpClientHandler to access TFS with the credentials of the running process, which is probably your account when executing locally and a service account when executing on the server.
Whithout this line, the specified NetworkCredential should be used to access TFS.

Related

Generate SPNEGO Token Failured

I tried to generate the token which can be used as the HTTP header to authenticate to the HDFS WebHDFS URL and Oozie REST API URL.
I referenced the url below to have the below code to generate the Negotiate token.
https://www.ibm.com/support/knowledgecenter/en/SS7JFU_8.5.5/com.ibm.websphere.express.doc/ae/tsec_SPNEGO_token.html
public class TokenCreation {
private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
public static byte[] genToken(String principal) {
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
byte[] spnegoToken = new byte[0];
try {
Oid spnegoMechOid = new Oid(SPNEGO_OID);
Oid krb5MechOid = new Oid(KERBEROS_OID);
GSSCredential clientGssCreds = null;
GSSManager manager = GSSManager.getInstance();
GSSName gssUserName = manager.createName(principal, GSSName.NT_USER_NAME, krb5MechOid);
clientGssCreds = manager.createCredential(gssUserName.canonicalize(krb5MechOid),
GSSCredential.INDEFINITE_LIFETIME,
krb5MechOid,
GSSCredential.INITIATE_ONLY);
clientGssCreds.add(gssUserName,
GSSCredential.INDEFINITE_LIFETIME,
GSSCredential.INDEFINITE_LIFETIME,
spnegoMechOid, GSSCredential.INITIATE_ONLY);
GSSName gssServerName = manager.createName(principal, GSSName.NT_USER_NAME);
GSSContext clientContext = manager.createContext(gssServerName.canonicalize(spnegoMechOid),
spnegoMechOid,
clientGssCreds,
GSSContext.DEFAULT_LIFETIME);
// optional enable GSS credential delegation
clientContext.requestCredDeleg(true);
// create a SPNEGO token for the target server
spnegoToken = clientContext.initSecContext(spnegoToken, 0, spnegoToken.length);
} catch (GSSException e) {
e.printStackTrace();
}
return spnegoToken;
}
But after running the above code, I always got the below prompt:
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.c.u.security.KrbUtils - after loginUserFromKeytab............AtoimcUser:HTTP/host1.exmaple.com#EXAMPLE.COM
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.app.oozie.OozieAppCaller - ->>>>>>User Name is HTTP/host1.exmaple.com#EXAMPLE.COM
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.app.oozie.OozieAppCaller - ->>>>>>Mode is KERBEROS
>>>KinitOptions cache name is /tmp/krb5cc_0
Kerberos username [root]: ^C^C^C
Kerberos password for root:
You can see at the end of the above output log.
The "Kerberos username" is always prompt to ask for username.
Also I have tried to manually run kinit the keytab.
and the above class can generate the token successfully.
But manually run kinit is NOT the way I wanted.
Would you please help it?
Thanks.
Kerberos and SPNEGO support in Java is cumbersome unfortunately.
I've created a small library to simplify some Kerberos use cases: https://github.com/bedrin/kerb4j
You can use it like this to generate SPNEGO token:
SpnegoClient spnegoClient = SpnegoClient.loginWithKeyTab("svc_consumer", "/opt/myapp/consumer.keytab");
URL url = new URL("http://api.provider.acme.com/api/operation1");
SpnegoContext context = spnegoClient.createContext("http://provider.acme.com"); // Will result in HTTP/provider.acme.com SPN
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization", context.createTokenAsAuthroizationHeader());

Azure media service job creation fails using rest api

Trying to consume Azure media service rest api. (following the tutorial : https://learn.microsoft.com/en-us/azure/media-services/media-services-rest-get-started)
Everything works fine until the point I try to create a Job. Sending the same request as in example (except asset id and token) and getting response :
Parsing request content failed due to: Make sure to only use property names that are defined by the type
Request:
POST https://wamsdubclus001rest-hs.cloudapp.net/api/Jobs HTTP/1.1
Connection: Keep-Alive
Content-Type: application/json
Accept: application/json; odata=verbose
Accept-Charset: UTF-8
Authorization: Bearer token -> here i send real token
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 3.0;NetFx
x-ms-version: 2.11
Content-Length: 458
Host: wamsdubclus001rest-hs.cloudapp.net
{
"Name":"TestJob",
"InputMediaAssets":[
{
"__metadata":{
"uri":"https://wamsdubclus001rest-hs.cloudapp.net/api/Assets('nb%3Acid%3AUUID%3A5168b52a-68ed-4df1-bac8-0648ce734ff6')"
}
}
],
"Tasks":[
{
"Configuration":"Adaptive Streaming",
"MediaProcessorId":"nb:mpid:UUID:ff4df607-d419-42f0-bc17-a481b1331e56",
"TaskBody":"<?xml version=\"1.0\" encoding=\"utf-8\"?><taskBody><inputAsset>JobInputAsset(0)</inputAsset> <outputAsset>JobOutputAsset(0)</outputAsset></taskBody>"
}
]
}
Response:
{
"error":{
"code":"",
"message":{
"lang":"en-US",
"value":"Parsing request content failed due to: Make sure to only use property names that are defined by the type"
}
}
}
It seems to be related with __metadata property. when I follow instruction from here : Creating Job from REST API returns a request property name error, the error changes:
"error":{
"code":"",
"message":{
"lang":"en-US",
"value":"Invalid input asset reference in TaskBody - "
}
}
}
Cant figure out whats wrong, thanks
Let me check on this, but it could be a couple issues that I have run into in the past.
First. Set both the Accept and Content-Type headers to:
"application/json; odata=verbose"
Next, double check that you are actually using the long underscore character on the metadata property. I've had issues where that was sending the wrong underscore character and it didn't match the property name.
Let me know if either of those helps.
It seems the issue was about "Content-Type". As I am using .net Core it was not easy to set the Conent-type as "application/json; odata=verbose".
1) Tried with RestSharp - dosnt support it, it cuts "odata=verbose" part out
2) Tried with Systsem.Net.Http.HttpClient -> Possible but difficult.
To add it as "Accept" :
MediaTypeWithQualityHeaderValue mtqhv;
MediaTypeWithQualityHeaderValue.TryParse("application/json;odata=verbose", out mtqhv);
client.DefaultRequestHeaders.Accept.Add(mtqhv);//ACCEPT header
To add it as "Content-Type" :
request.Content = new StringContent(content,
System.Text.Encoding.UTF8); //CONTENT-TYPE header -> default type will be text/html
request.Content.Headers.Clear(); // need to clear it - it will fail otherwise
request.Content.Headers.TryAddWithoutValidation("Content-Type","application/json;odata=verbose");

Performing http web request to a server requiring SAML authentication

I have simple app that is trying to do a http web request to a server that requires SAML authentication. Authenticated users will get a http response header with a special token, which is what I need to ultimately get.
My app is .net based and does a pretty simple http web request. It does the request then parses the response header. I later traverse the header for the specific token I need:
...
try
{
WindowsIdentity identity = HttpContext.User.Identity as WindowsIdentity;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.UseDefaultCredentials = true;
req.AllowAutoRedirect = true;
req.Timeout = 30000;
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
if (response == null)
{
throw new Exception("No HTTP Response");
}
StringBuilder sb = new StringBuilder();
Byte[] buffer = new byte[8192];
Stream rStream = response.GetResponseStream();
int count = 1;
do
{
count = rStream.Read(buffer, 0, buffer.Length);
if (count != 0)
{
sb.Append(Encoding.UTF8.GetString(buffer, 0, count));
}
} while (count > 0);
...
The problem is that the server I'm requesting requires SAML authentication. It redirects to an ADFS server upon request. My app server currently uses kerberos authentication but I can enable it to do SAML as well. Both servers use the same IdP (ADFS) and are in the same enterprise.
My question is - since my app can also do SAML on the same IdP, is there anyway I could get the necessary claims to connect directly into the destination server?

Salesforce rest api INVALID_SESSION_ID

I'm trying to connect my asp.net REST api to salesforce. I'm succesfully going through authentification, but when I start to send POST requests, I'm getting an error
{"errorCode":"INVALID_SESSION_ID","message":"Session expired or invalid"}
Here is my POST request:
//SFServerUrl = "https://na17.salesforce.com/services/";
//url = "data/v28.0/sobjects/Account";
ASCIIEncoding ascii = new ASCIIEncoding();
byte[] postBytes = ascii.GetBytes(postBody);
HttpWebRequest request = WebRequest.Create(Globals.SFServerUrl + url) as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/json";
request.ContentLength = postBytes.Length;
Stream postStream = request.GetRequestStream();
postStream.Write(postBytes, 0, postBytes.Length);
HttpCookie cookie = HttpContext.Current.Request.Cookies[Globals.SFCookie];
var ticket = FormsAuthentication.Decrypt(cookie.Value);
string authToken = ticket.UserData;
request.Headers.Add("Authorization", "Bearer " + authToken);
postStream.Close();
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[8192];
Stream resStream = response.GetResponseStream();
string tempString = null;
int count = 0;
do
{
count = resStream.Read(buf, 0, buf.Length);
if (count != 0)
{
tempString = Encoding.ASCII.GetString(buf, 0, count);
sb.Append(tempString);
}
}
while (count > 0);
return new Tuple<bool, string>(true, sb.ToString());
When I'm trying to send GET request - I recieve 200 response.
Also, I've tried to send a POST Request with the same token from Simple Rest Client and it get's 200 response. I tried to change my "Authorization : Bearer" Header to "Authorization : Oauth", but nothing changed. I also tried to catch this error, get refresh token and send a request again with refreshed token, but nothing changed. Please, help me with this.
Using workbench I was able to POST the following JSON to /services/data/v29.0/sobjects/Account and create a new Account.
{
"Name" : "Express Logistics and Transport"
}
Raw Response
HTTP/1.1 201 Created
Date: Sun, 07 Sep 2014 21:32:06 GMT
Set-Cookie: BrowserId=_HC-bzpTQABC1237vFu2hA;Path=/;Domain=.salesforce.com;Expires=Thu, 06-Nov-2014 21:32:06 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Sforce-Limit-Info: api-usage=209/15000
Location: /services/data/v29.0/sobjects/Account/0010000000000001AAA
Content-Type: application/json;charset=UTF-8
Content-Encoding: gzip
Transfer-Encoding: chunked
{
"id" : "0010000000000001AAA",
"success" : true,
"errors" : [ ]
}
Things to check:
Your URL. It appears to be missing the leading /services
Is SFServerUrl the same Salesforce pod/server that the Session Id was issued to? If the Session Id came from another pod then it would be invalid on na17.
How did you create the Session Id? If you used OAuth, what scopes did you request?
Is the Session Id coming out of the cookie valid?
Has something else using the same Session Id called logout and invalidated the session?
Incidentally, the Salesforce StackExchange site is a great place to ask Salesforce specific questions.
See also:
Using REST API Resources - Create a Record
The problem was that I added Headers after Content. When I switched these lines of code everything worked.

lua socket POST

I have a hard time using lua socket generic POST. I'm trying to POST to a web along with a body. But the body output i got is 1. Here's my code
local http = require "socket.http"
local ltn12 = require "ltn12"
local util = require "util"
local reqbody = "anid=&protocol=1&guid=dfe49e55b63f2cf93eb9aabe44b6d9dc5286bbbedfcbf1c75b95f7a4f7439029&d_type=phone&os_version=6.1&ate=1&asid=079ABF64-A23A-4E3B-9000-19A4A608CCBE&affiliate=&modin=7c78d075f379db2f40c9f68df857cb87&os=ios&d_id=107b2734fdb7898251f62d229168484a9d14f7fb654d02d957b30c9f22bb094c&d_code=1E5D02FF-63F3-43A0-A2BF-80E63E00F76C&pn_device_id=&name_hint=iPhone%20Simulator&d_sig=dfe49e55b63f2cf93eb9aabe44b6d9dc5286bbbedfcbf1c75b95f7a4f7439029&hdid=62624a01f8715f2b838224a4a285746d&tracker=&appid=536381662&odin=1da61c680b672c4e114df45cd5f8f0aa9b088338&model=iPhone%20Simulator&ver=15&campaign=&imei=&store_type=apple&"
local respbody = {}
local body, code, headers, status = http.request {
method = "POST",
url = "https://freshdeck.idle-gaming.com/api/guest_session/",
source = ltn12.source.string(reqbody),
headers =
{
["Accept"] = "*/*",
["Accept-Encoding"] = "gzip, deflate",
["Accept-Language"] = "en-us",
["Content-Type"] = "application/x-www-form-urlencoded",
["content-length"] = string.len(reqbody)
},
sink = ltn12.sink.table(respbody)
}
LOGINFO('body:' .. tostring(body))
LOGINFO('code:' .. tostring(code))
LOGINFO('headers:' .. util.tableToString(headers))
LOGINFO('status:' .. tostring(status))
and below is the output
body:1
code:200
headers: "set-cookie": "config_version=887; expires=Sat, 29-Jun-2013 19:07:09 GMT; Max-Age=86400; Path=/"
"date": "Fri, 28 Jun 2013 19:07:09 GMT"
"ed-config-version": "887"
"content-encoding": "gzip"
"cache-control": "private, no-cache, no-store, must-revalidate"
"connection": "Close"
"vary": "Cookie"
"content-length": "52"
"pragma": "no-cache"
"content-type": "application/json; charset=utf-8"
"server": "nginx/1.2.7"
status:HTTP/1.1 200 OK
I don't know why the body returns 1, any ideas?
Thank you in advance for your help.
http.request has two forms, The simple form downloads a URL using the GET or POST method and is based on strings.
http.request(url [, body])
The generic form performs any HTTP method and is LTN12 based.
http.request{
url = string,
[sink = LTN12 sink,]
[method = string,]
[headers = header-table,]
[source = LTN12 source],
[step = LTN12 pump step,]
[proxy = string,]
[redirect = boolean,]
[create = function]
}
You are using the generic form, and according to the document, the first return value is supposed to be 1.
In case of failure, the function returns nil followed by an error message. If successful, the simple form returns the response body as a string, followed by the response status code, the response headers and the response status line. The generic function returns the same information, except the first return value is just the number 1 (the body goes to the sink).