I have a working Spring Boot with AngularJs app based on https://spring.io/guides/tutorials/spring-security-and-angular-js/
I am using Basic Authentication and I would like to write an integration test for it. Currently, I always get a 403 status code with the message:
Expected CSRF token not found. Has your session expired?
This is my test:
#Test
public void givenAdmin_deleteOfBookIsAllowed() {
Response response = given().auth().preemptive().basic("admin", "admin").get("/api/user/");
response.then().log().all();
String sessionId = response.sessionId();
String token = response.cookie("XSRF-TOKEN");
Book book = Books.randomBook();
bookRepository.save(book);
given()
.sessionId(sessionId)
.cookie("XSRF-TOKEN", token)
.header("X-XSRF-TOKEN", token)
.pathParam("id", book.getId().getId())
.when().delete("/api/books/{id}")
.then().statusCode(HttpStatus.SC_NO_CONTENT);
}
I am using a custom token repo per the tutorial:
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
This is the request/response from the first call:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Set-Cookie: JSESSIONID=7020E58A8D6DC2C883FD5D6BD086512A; Path=/; HttpOnly
Set-Cookie: XSRF-TOKEN=6c44ae09-73f9-4115-bbbf-b01773ec1b91; Path=/
X-Application-Context: application:staging:0
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Content-Encoding: gzip
Vary: Accept-Encoding
Date: Fri, 08 Jan 2016 07:43:21 GMT
{
"details": {
"remoteAddress": "127.0.0.1",
"sessionId": "7020E58A8D6DC2C883FD5D6BD086512A"
},
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"authenticated": true,
"principal": {
"password": null,
"username": "admin",
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"credentials": null,
"name": "admin"
}
And from the second call:
Request method: DELETE
Request path: http://localhost:64588/api/books/04ad6d12-9b59-4ade-9a8a-e45daccb1f61
Proxy: <none>
Request params: <none>
Query params: <none>
Form params: <none>
Path params: id=04ad6d12-9b59-4ade-9a8a-e45daccb1f61
Multiparts: <none>
Headers: X-XSRF-TOKEN=6c44ae09-73f9-4115-bbbf-b01773ec1b91
Accept=*/*
Cookies: JSESSIONID=7020E58A8D6DC2C883FD5D6BD086512A
XSRF-TOKEN=6c44ae09-73f9-4115-bbbf-b01773ec1b91
Body: <none>
HTTP/1.1 403 Forbidden
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Content-Encoding: gzip
Vary: Accept-Encoding
Date: Fri, 08 Jan 2016 07:44:21 GMT
{
"timestamp": "2016-01-08T07:44:21.963+0000",
"status": 403,
"error": "Forbidden",
"message": "Expected CSRF token not found. Has your session expired?",
"path": "/api/books/04ad6d12-9b59-4ade-9a8a-e45daccb1f61"
}
You need to do 2 GET before post to use spring security CSRF protection in your rest client or integration test.
Make a GET request to login. This will return the JSESSIONID token and XSRF-TOKEN tokens. If you use the returned XSRF-TOKEN to POST it will fail, because we got it using empty/false JSESSIONID.
Get a useful XSRF-TOKEN from the second GET, using JSESSIONID from previous request.
Now you can use XSRF-TOKEN for your POST.
Example integration test for rest assured with basic auth and CSRF protection:
//1) get sessionId
Response response =
given().auth().preemptive().basic(userName, userPassword).contentType(JSON).
when().get(PREFIX_URL + "/user").
then().log().all().extract().response();
String jsessionidId = response.getSessionId();//or response.cookie("JSESSIONID");
//2) get XSRF-TOKEN using new/real sessionId
response =
given().
sessionId(jsessionidId).//or cookie("JSESSIONID", jsessionidId).
contentType(JSON).
when().get(PREFIX_URL + "/user").
then().log().all().extract().response();
//3) post data using XSRF-TOKEN
given().log().all().
sessionId(jsessionidId).//or cookie("JSESSIONID", jsessionidId).
header("X-XSRF-TOKEN", response.cookie("XSRF-TOKEN")).
queryParam("param",paramValue)).
body(someData).
contentType(JSON).
when().
post(PREFIX_URL + "/post_some_data").
then().
log().all().assertThat().statusCode(200);
Related
When queue-ing a build manually using TFS2018 the shelveset name is not showing the source branch name in all cases. Sometimes it is filled out sometimes not. Since I am picking up build variables for the source branch
$(Build.SourceBranch)
$(Build.SourceBranchName)
They will be empty if the Shelveset name is empty.
Is it possible to set the shelveset name default as the sourcebranch using the API when creating the build definition?
Is there another build variable that I can use to get the source path for the currently built solution?
UPDATE So I am trying to update the source branches using the build api. However when called I get a
{StatusCode: 405, ReasonPhrase: 'Method Not Allowed', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Pragma: no-cache
X-TFS-ProcessId:
ActivityId:
X-TFS-Session:
X-VSS-E2EID:
X-FRAME-OPTIONS: SAMEORIGIN
X-VSS-UserData: :user
Persistent-Auth: true
Lfs-Authenticate: NTLM
X-Content-Type-Options: nosniff
Cache-Control: no-cache
Date: Fri, 09 Mar 2018 14:37:16 GMT
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"
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Length: 93
Allow: GET
Content-Type: application/json; charset=utf-8
Expires: -1
}}
for the following code....
internal void UpdateSourceBranches(List<BuildDefinition> defs)
{
using (var handler = new HttpClientHandler { Credentials = new NetworkCredential(tfsUser, tfsPass) })
using (var client = new HttpClient(handler))
{
try
{
client.BaseAddress = new Uri(tfsServer);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
foreach (var def in defs)
{
var buildId = def.Id;
var sourceBranch = $"$/{def.Repository.Name}/{def.Project.Name}";
var parameters = new Dictionary<string, string> { { "BuildConfiguration", "release" },
{ "BuildPlatform", "x86|x64|ARM" },
{ "system.debug", "true" }
};
var jsonParams = JsonConvert.SerializeObject(parameters);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("id", buildId.ToString()),
new KeyValuePair<string, string>("sourceBranch", sourceBranch),
new KeyValuePair<string, string>("parameters", jsonParams)
});
var response = client.PostAsync($"DefaultCollection/{def.Repository.Name}/_apis/build/builds?api-version=3.0-preview.1", content);
var s = response.Result;
}
}
catch (Exception ex)
{
}
}
}
You should use Queue a build api to set SourceBranch, for example:
POST http://TFS2018:8080/tfs/DefaultCollection/{project}/_apis/build/builds?api-version=2.0
Content-Type: application/json
{
"definition": {
"id": 47
},
"sourceBranch":"$/CeceScrum/TestCaseProject",
"parameters":"{\"BuildConfiguration\":\"release\",\"BuildPlatform\":\"any cpu\",\"system.debug\":\"false\"}"
}
If you want to choose a shelveset to build, then the api looks like below:
POST http://TFS2018:8080/tfs/DefaultCollection/{project}/_apis/build/builds?api-version=2.0
Content-Type: application/json
{
"definition": {
"id": 47
},
"sourceBranch":"ceceShelveset;domain\\username",
"parameters":"{\"BuildConfiguration\":\"release\",\"BuildPlatform\":\"any cpu\",\"system.debug\":\"false\"}"
}
I am using TFS2018 and calling the build api like this
internal void UpdateSourceBranches(List<BuildDefinition> defs)
{
using (var handler = new HttpClientHandler { Credentials = new NetworkCredential(tfsUser, tfsPass) })
using (var client = new HttpClient(handler))
{
try
{
client.BaseAddress = new Uri(tfsServer);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
foreach (var def in defs)
{
var buildId = def.Id;
var sourceBranch = $"$/{def.Repository.Name}/{def.Project.Name}";
var parameters = new Dictionary<string, string> { { "BuildConfiguration", "release" },
{ "BuildPlatform", "x86|x64|ARM" },
{ "system.debug", "true" }
};
var jsonParams = JsonConvert.SerializeObject(parameters);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("id", buildId.ToString()),
new KeyValuePair<string, string>("sourceBranch", sourceBranch),
new KeyValuePair<string, string>("parameters", jsonParams)
});
var response = client.PostAsync($"DefaultCollection/{def.Repository.Name}/_apis/build/builds?api-version=3.0-preview.1", content);
var s = response.Result;
}
}
catch (Exception ex)
{
}
}
}
But getting the following response
{StatusCode: 405, ReasonPhrase: 'Method Not Allowed', Version: 1.1,
Content: System.Net.Http.StreamContent, Headers: { Pragma: no-cache
X-TFS-ProcessId: ActivityId: X-TFS-Session: X-VSS-E2EID:
X-FRAME-OPTIONS: SAMEORIGIN X-VSS-UserData: :user Persistent-Auth:
true Lfs-Authenticate: NTLM X-Content-Type-Options: nosniff
Cache-Control: no-cache Date: Fri, 09 Mar 2018 14:37:16 GMT 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" Server:
Microsoft-IIS/10.0 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET
Content-Length: 93 Allow: GET Content-Type: application/json;
charset=utf-8 Expires: -1 }}
Any idea why I am getting Method Not Allowed ?
Here is the bad response:
{StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content:
System.Net.Http.StreamContent, Headers: { Pragma: no-cache
X-TFS-ProcessId: xxxxxx-xxxxx-xxxxx-xxxxxx-xxxxxxxxxx ActivityId:
xxxxxx-xxxxx-xxxxx-xxxxxx-xxxxxxxxxx X-TFS-Session:
xxxxxx-xxxxx-xxxxx-xxxxxx-xxxxxxxxxx X-VSS-E2EID:
xxxxxx-xxxxx-xxxxx-xxxxxx-xxxxxxxxxx X-FRAME-OPTIONS: SAMEORIGIN
X-VSS-UserData: xxxxxx-xxxxx-xxxxx-xxxxxx-xxxxxxxxxx:user
Persistent-Auth: true Lfs-Authenticate: NTLM
X-Content-Type-Options: nosniff Cache-Control: no-cache Date: Tue,
13 Mar 2018 09:21:53 GMT P3P: CP="...multiple keywords" Server:
Microsoft-IIS/10.0 X-AspNet-Version: 4.0.30319 X-Powered-By:
ASP.NET Content-Length: 547 Content-Type: application/json;
charset=utf-8 Expires: -1 }}
Sounds like your POST is the issue, try using a GET
You should use api-version=2.0 instead of 3.0-preview.1.
Update code snippet:
string con = "{\"definition\": {\"id\": 47},\"sourceBranch\":\"$/CeceScrum/TestCaseProject\",\"parameters\":\"{\\\"BuildConfiguration\\\":\\\"release\\\",\\\"BuildPlatform\\\":\\\"any cpu\\\",\\\"system.debug\\\":\\\"false\\\"}\"}";
var patchValue = new StringContent(con, Encoding.UTF8, "application/json");
HttpClient httpClient = new HttpClient();
string _credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "domain\\username", "password")));
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", _credentials);
var method = new HttpMethod("POST");
var request = new HttpRequestMessage(method, "http://TFS2018:8080/tfs/DefaultCollection/CeceScrum/_apis/build/builds?api-version=2.0") { Content = patchValue };
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = httpClient.SendAsync(request).Result;
string re= response.Content.ReadAsStringAsync().Result;
I am having issues with post a form to MVC, but only when the site is hosted inside of azure. When it is hosted locally inside of IIS OR inside of the compute emulator it works fine.
The request is posted, however the fields in the form are not being mapped to the action's parameters.
Is there something funky I need to do with the azure deployment?
MultipartFormDataContent data = new MultipartFormDataContent();
if (!String.IsNullOrEmpty(about))
{
data.Add(new StringContent(about), "about");
}
data.Add(new StringContent(displayName), "displayName");
data.Add(new StringContent(name), "name");
data.Add(new StringContent(email), "email");
data.Add(new StringContent(phone), "phone");
data.Add(new StringContent(isPublic ? "true": "false"), "isPublic");
data.Add(new StringContent(isPrimary ? "true" : "false"), "isPrimaryProfile");
if (picture != null)
{
byte[] imageBytes = await picture.GetBtyeFromFile();
string imageString = Convert.ToBase64String(imageBytes);
data.Add(new StringContent(imageString), "picture");
}
Uri uri = new Uri(url, UriKind.Absolute);
HttpResponseMessage responseMessage = await client.PostAsync(uri, data);
On the server side everything seems to be arriving correctly. Here is the body that I pulled out of the InputStream:
--614f8827-0cfe-48a6-a819-e6e9acdccae0
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=displayName
display
--614f8827-0cfe-48a6-a819-e6e9acdccae0
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=name
Test Seller Account
--614f8827-0cfe-48a6-a819-e6e9acdccae0
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=email
email
--614f8827-0cfe-48a6-a819-e6e9acdccae0
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=phone
phone
--614f8827-0cfe-48a6-a819-e6e9acdccae0
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=isPublic
false
--614f8827-0cfe-48a6-a819-e6e9acdccae0
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=isPrimaryProfile
true
--614f8827-0cfe-48a6-a819-e6e9acdccae0--
And are the headers:
ALL_RAW - Content-Length: 880
Content-Type: multipart/form-data; boundary="614f8827-0cfe-48a6-a819-e6e9acdccae0"
Host: [my server]
See: How to upload files to Asp.Net MVC 4.0 action running in IIS Express with HttpClient class included in .Net 4.0
Basically, you have to quote your param names:
data.Add(new StringContent(displayName), #"""displayName""");
I am trying to subscribe a user to a youtube channel. So the user logs in to an iOS app, hits subscribe, and I would subscribe the user to a predefined channel.
So I have an app created with the YouTube Data API v3 service enabled.
scope: kGTLAuthScopeYouTube
GTLServiceYouTube *service = self.youTubeService;
GTLYouTubeSubscriptionSnippet* snippet = [GTLYouTubeSubscriptionSnippet object];
snippet.channelId = #"UCGRjJrpD2bmk9Ilq6nq80qg";
GTLYouTubeSubscription* subscription = [GTLYouTubeSubscription object];
subscription.snippet = snippet;
GTLQueryYouTube *query = [GTLQueryYouTube queryForSubscriptionsInsertWithObject:subscription part:#"contentDetails,snippet"];
[service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLYouTubeSubscription *subscription,
NSError *error) {
/* Callback */
NSLog(#"subscription:%#", subscription);
NSLog(#"error:%#", error);
}];
the server returns:
error:Error Domain=com.google.GTLJSONRPCErrorDomain Code=-32500 "The operation couldn’t be completed. (Required)" UserInfo=0x1005d3c20 {error=Required, NSLocalizedFailureReason=(Required), GTLStructuredError=GTLErrorObject 0x10054faa0: {message:"Required" data:[1] code:-32500}}
Any ideas why would this happen?
I am using the sample youtube app.
The sample url on https://developers.google.com/youtube/v3/docs/subscriptions/insert#examples
works correctly and from what I can tell I am doing the same thing above in objc code.
POST https://www.googleapis.com/youtube/v3/subscriptions?part=contentDetails%2Csnippet&fields=snippet&key={YOUR_API_KEY}
Content-Type: application/json
Authorization: Bearer ya29.AHES6ZQSBniofZhyVX4kfCn0-gVKKeiGayMcQHjTfWxMyffndRus7w
X-JavaScript-User-Agent: Google APIs Explorer
{
"snippet": {
"resourceId": {
"channelId": "UCDPM_n1atn2ijUwHd0NNRQw"
}
}
}
200 OK
Hide headers -
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Encoding: gzip
Content-Length: 286
Content-Type: application/json; charset=UTF-8
Date: Fri, 14 Jun 2013 11:28:26 GMT
Etag: "2vd4g3cVsHAtTjJSdUMaBo1PBVE/Rv5ixqWURoAy7lbp6z3jpkm7IOE"
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Server: GSE
{
"snippet": {
"publishedAt": "2013-06-14T11:28:26.000Z",
"title": "ColdplayVEVO",
"description": "",
"resourceId": {
"kind": "youtube#channel",
"channelId": "UCDPM_n1atn2ijUwHd0NNRQw"
},
"channelId": "UCQIKfhQEozSerNr3go189mw",
"thumbnails": {
"default": {
"url": "https://i1.ytimg.com/i/DPM_n1atn2ijUwHd0NNRQw/1.jpg?v=c2f0dd"
},
"high": {
"url": "https://i1.ytimg.com/i/DPM_n1atn2ijUwHd0NNRQw/mq1.jpg?v=c2f0dd"
}
}
}
}
UPDATE 1
Enabled loggin - see details below
youtube.subscriptions.insert
2013-06-19 08:45:52 +0000
Request: POST https://www.googleapis.com/rpc?prettyPrint=false
Request headers:
Accept: application/json-rpc
Authorization: Bearer _snip_
Cache-Control: no-cache
Content-Type: application/json-rpc; charset=utf-8
User-Agent: com.boxonline.tpp.bol.think/1.0 google-api-objc-client/2.0 MacOSX/10.8.3 (gzip)
Request body: (214 bytes)
{
"jsonrpc" : "2.0",
"method" : "youtube.subscriptions.insert",
"id" : "gtl_1",
"params" : {
"fields" : "snippet",
"part" : "contentDetails,snippet",
"resource" : {
"snippet" : {
"channelId" : "UCGRjJrpD2bmk9Ilq6nq80qg"
}
}
},
"apiVersion" : "v3"
}
Response: status 200
Response headers:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Encoding: gzip
Content-Length: 131
Content-Type: application/json; charset=UTF-8
Date: Wed, 19 Jun 2013 08:45:52 GMT
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Server: GSE
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Response body: (152 bytes)
{
"error" : {
"message" : "Required",
"data" : [
{
"reason" : "publisherRequired",
"message" : "Required",
"domain" : "youtube.subscription"
}
],
"code" : -32500
},
"id" : "gtl_1"
}
The solution was to replace:
GTLYouTubeSubscriptionSnippet* snippet = [GTLYouTubeSubscriptionSnippet object];
snippet.channelId = #"UCGRjJrpD2bmk9Ilq6nq80qg";
with this:
GTLYouTubeSubscriptionSnippet* snippet = [GTLYouTubeSubscriptionSnippet object];
GTLYouTubeResourceId* resourceObject = [GTLYouTubeResourceId object];
resourceObject.channelId = #"UCGRjJrpD2bmk9Ilq6nq80qg";
my application should sent structure via json,
that functions works fine:
function send_json()
{
var formData = form2object('myForm');
var json_data = JSON.stringify(formData, null, '\t');
$.post("", json_data);
}
It create json_data and send it via post.
But I don't understand how to catch this data on another side, can you help me ?
I should take this structure and make some actions with it.
when i send data i got json:
{ "type": "test", "ttl": "1", "amount": "1", "rules": { "rule1": "1", "value1": "4444", "stackcount1": "1", "rule2": "2", "value2": "333", "stackcount2": "2" }}
In the contoller i wrote this:
class AdminController < ApplicationController
def index
#json_string = params[:amount]
if !#json_sting.nil?
#json_string = JSON.parse(params[:amount])
end
end
end
in the view(haml) i wrote:
=#json_string
It doesn't work !
But wireshark shows:
POST /admin/ HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1
Accept: */*
Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: http://localhost:3000/admin/
Content-Length: 192
Cookie: _GiftSenderTool_session=BAh7BzoQX2NzcmZfdG9rZW4iMTVyNVJCMTBvcGpKSk5OcFN4bW15YVlpRlF1TUNtb1gwZkY0bTRuRHlsNnM9Ig9zZXNzaW9uX2lkIiU4ZjFjNTNjNTY0MTQ1MGExNmFiODcxZGEyYzU5ZTkxMg%3D%3D--f21bf3b96edc3bc551c8de68acc6da17685d58b4
Pragma: no-cache
Cache-Control: no-cache
{
."type": "test",
."ttl": "1",
."amount": "1",
."rules": {
.."rule1": "1",
.."value1": "4444",
.."stackcount1": "1"
.},
."authenticity_token": "n3B6D/Km9zOgOzlosK/OLHyTCDLk3MyCTlTBinfQcnY="
}
It means that i can't get data.
you have to catch your JSON object in some rails controller action. If your JSON string looks like this:
{ "user": { "id": 1, "name": "Tom" } }
then do it like this:
require "json"
...
def some_action
user = JSON.parse(params[:user])
puts "User id is #{user[:id]}"
...
end