404 for confluence json-rpc call - confluence

I'm attempting to write against the json-rpc API for confluence (in java). However, I'm getting a 404. I know that I'm getting to the server and authenticating (Basic auth) via the logs:
2013-10-08 10:48:59,586 DEBUG [http-8090-2] [atlassian.seraph.filter.BaseLoginFilter] doFilter doFilter : ____ Attempting login for : '/confluence/rpc/json-rpc/confluenceservice-v2'
2013-10-08 10:48:59,586 DEBUG [http-8090-2] [atlassian.seraph.filter.PasswordBasedLoginFilter] login login : No user name or password was returned. No authentication attempt will be made. User may still be found via a SecurityFilter later.
2013-10-08 10:48:59,586 DEBUG [http-8090-2] [atlassian.seraph.filter.BaseLoginFilter] doFilter doFilter : Login completed for 'null' - os_authstatus = 'null'
2013-10-08 10:48:59,587 DEBUG [http-8090-2] [atlassian.seraph.filter.SecurityFilter] doFilter doFilter : Storing the originally requested URL (atlassian.core.seraph.original.url=/confluence/rpc/json-rpc/confluenceservice-v2)
(that's good, see the rpc call)
2013-10-07 16:25:22,580 DEBUG [http-8090-2] [atlassian.seraph.auth.DefaultAuthenticator] login login : 'george' has been authenticated
2013-10-07 16:25:22,582 DEBUG [http-8090-2] [atlassian.seraph.auth.DefaultAuthenticator] authoriseUserAndEstablishSession authoriseUser : 'george' can login according to the RoleMapper
2013-10-07 16:25:22,584 DEBUG [http-8090-2] [net.sf.hibernate.SQL] log update logininfo set CURFAILED=?, TOTALFAILED=?, SUCCESSDATE=?, PREVSUCCESSDATE=?, FAILEDDATE=?, USERNAME=? where id=?
2013-10-07 16:25:22,586 DEBUG [http-8090-2] [atlassian.seraph.auth.DefaultAuthenticator] getUserFromBasicAuthentication getUserFromSession : Authenticated 'george' via Basic Auth
2013-10-07 16:25:22,587 DEBUG [http-8090-2] [atlassian.seraph.filter.SecurityFilter] doFilter doFilter : Setting Auth Context to be 'george'
But the response to a post to the URL
http://my.confluence.host.com:8090/confluence/rpc/json-rpc/confluenceservice-v2
with a body of
"{ \"jsonrpc\" : \"2.0\", \"method\" : \"getServerInfo\", \"id\" : 12345}"
returns with a 404.
The next log entry is
2013-10-08 10:48:59,626 DEBUG [http-8090-2] [atlassian.seraph.filter.SecurityFilter] doFilter doFilter : Storing the originally requested URL (atlassian.core.seraph.original.url=/fourohfour.action)
Plugin is enabled.
Thanks for any help!
The code:
#Test
public void test() {
RestTemplate rt = new TestRestTemplate();
String url = "http://my.confluence.host.com:8090/confluence/rpc/json-rpc/confluenceservice-v2";
rt.getMessageConverters().add(new MappingJacksonHttpMessageConverter());
rt.getMessageConverters().add(new StringHttpMessageConverter());
String body = "{ \"jsonrpc\" : \"2.0\", \"method\" : \"getServerInfo\", \"id\" : 12345}";
String returnValue = rt.postForObject(url, body, String.class);
}
private static final class TestRestTemplate extends RestTemplate {
#Override
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = super.createRequest(url, method);
String authentication = Base64.encode("george:password".getBytes());
request.getHeaders().add("Authorization", "Basic " + authentication);
return request;
}
}

Turns out the #(&*$ documentation was wrong and the reason why I was getting a 404 was because the url was not
http://host:port/confluence/rpc/json-rpc/confluenceservice-v2
but
http://host:port/rpc/json-rpc/confluenceservice-v2
So.. I was getting a 404 because... it was a 404. Code works great except that I had to add a content-type header for application/json.

Related

How to make a RESTful call using Basic Authentication in apache camel?

I have an apache camel application that requires sending log files to an endpoint and this requires Basic Authentication. I was able to pass the authMethod, authusername and authPassword to the url as specified in the camel documentation but the challange I'm having is that I keep getting null response from the endpoint after starting the application.
However, the same endpoint returns response code and response body using postman.
Below is my code:
from("{{routes.feeds.working.directory}}?idempotent=true")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
String fileName = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
File file = exchange.getIn().getBody(File.class);
multipartEntityBuilder.addPart("file",
new FileBody(file, ContentType.MULTIPART_FORM_DATA, fileName));
exchange.getOut().setBody(multipartEntityBuilder.build());
Message out = exchange.getOut();
int responseCode = out.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
log.info("response code "+responseCode);
}
})
.setHeader(Exchange.HTTP_QUERY,
constant("authMethod=Basic&authUsername="+username+"&authPassword="+password+""))
.to(TARGET_WITH_AUTH +"/"+uuid+"/files")
.log(LoggingLevel.DEBUG, "response code >>>>"+Exchange.HTTP_RESPONSE_CODE)
.log(LoggingLevel.INFO, "RESPONSE BODY ${body}")
.end();
Kindly help review and advise further
For HTTP basic authentication I use this before sending a request
<setHeader headerName="Authorization">
<constant>Basic cm9vdDpyb290</constant>
</setHeader>
cm9vdDpyb290 - Encoded Base64 root:root(username and password) string
This was fixed by using httpClient to send my requests with Basic Authentication. Apparently, authMethod in apache camel doesn't send the credentials along with the Post Request and that's why I was getting the initial 401 response code.
Thank y'all for your contributions.

RESTful client in Unity - validation error

I have a RESTful server created with ASP.Net and am trying to connect to it with the use of a RESTful client from Unity. GET works perfectly, however I am getting a validation error when sending a POST request. At the same time both GET and POST work when sending requests from Postman.
My Server:
[HttpPost]
public IActionResult Create(User user){
Console.WriteLine("***POST***");
Console.WriteLine(user.Id+", "+user.sex+", "+user.age);
if(!ModelState.IsValid)
return BadRequest(ModelState);
_context.Users.Add(user);
_context.SaveChanges();
return CreatedAtRoute("GetUser", new { id = user.Id }, user);
}
My client:
IEnumerator PostRequest(string uri, User user){
string u = JsonUtility.ToJson(user);
Debug.Log(u);
using (UnityWebRequest webRequest = UnityWebRequest.Post(uri, u)){
webRequest.SetRequestHeader("Content-Type","application/json");
yield return webRequest.SendWebRequest();
string[] pages = uri.Split('/');
int page = pages.Length - 1;
if (webRequest.isNetworkError || webRequest.isHttpError){
Debug.Log(pages[page] + ":\nReceived: " + webRequest.downloadHandler.text);
}
else{
Debug.Log(pages[page] + ":\nReceived: " + webRequest.downloadHandler.text);
}
}
}
I was trying both with the Json conversion and writing the string on my own, also with the WWWForm, but the error stays.
The error says that it's an unknown HTTP error. When printing the returned text it says:
"One or more validation errors occurred.","status":400,"traceId":"|b95d39b7-4b773429a8f72b3c.","errors":{"$":["'%' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0."]}}
On the server side it recognizes the correct method and controller, however, it doesn't even get to the first line of the method (Console.WriteLine). Then it says: "Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.ValidationProblemDetails'".
Here're all of the server side messages:
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 POST http://localhost:5001/user application/json 53
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'TheNewestDbConnect.Controllers.UserController.Create (TheNewestDbConnect)'
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
Route matched with {action = "Create", controller = "User"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult Create(TheNewestDbConnect.Data.Entities.User) on controller TheNewestDbConnect.Controllers.UserController (TheNewestDbConnect).
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.ValidationProblemDetails'.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
Executed action TheNewestDbConnect.Controllers.UserController.Create (TheNewestDbConnect) in 6.680400000000001ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'TheNewestDbConnect.Controllers.UserController.Create (TheNewestDbConnect)'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 11.3971ms 400 application/problem+json; charset=utf-8
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
I have no idea what is happening and how to solve it. Any help will be strongly appreciated!
Turned out I was just missing an upload handler. Adding this line solved it: webRequest.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(JsonObject));

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());

Integration Tests fail with JWT Authorization on OpenLiberty

Integration Tests (production code works well) fail while requesting REST endpoints secured with #RolesAllowed.
Following error is thrown:
[5/20/19 8:44:21:363 CEST] 00000109 com.ibm.ws.security.jaspi.JaspiServiceImpl I CWWKS1652A: Authentication failed with status AuthStatus.SEND_FAILUR for the web request
/banking/users/bed6109f-ef8a-47ec-8fa4-e57c71415a10. The user defined Java Authentication SPI for Containers (JASPIC) service null has determined that the authentication data is not valid.
Project is based on OpenLiberty with JWT. The difference is in the UI part. My UI is based on Angular, so for authentication (JWT issuing) following REST Endpoint is used:
#RequestScoped
#Path("/tokens")
#PermitAll
public class AuthResource {
#Inject
private SecurityContext securityContext;
#Inject
private AuthService authService;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getJwt() {
if (securityContext.isCallerInRole("USER") || securityContext.isCallerInRole("ADMIN")) {
String name = securityContext.getCallerPrincipal().getName();
AuthPojo authPojo = authService.createJwt(name);
return Response.ok(authPojo).build();
}
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
So:
UI (Angular) calls https://localhost:5051/tokens with Header "Authorization: Basic ENCODED_USERNAME_PASSWORD"
Backend responds with newly generated JWT Token in body and Header "Set-Cookie: LtpaToken2=SOME_TOKEN; Path=/; HttpOnly"
UI uses this token for all other requests against REST Endpoints annotated with "#RolesAllowed({"ADMIN", "USER" })"
Once again, in production code, all this schema works well, but Integration Tests fail.
Here is Integration Test code:
public class MyResourceIT {
private static final String URL = "https://localhost:" +
System.getProperty("liberty.test.ssl.port") + "/users/" + USER_ID1;
private String authHeader;
#Before
public void setup() throws Exception {
authHeader = "Bearer " + new JwtVerifier().createAdminJwt(USER_NAME1);
}
#Test
public void getUserAndAccounts() {
Response response = HttpClientHelper.processRequest(URL, "GET", null, authHeader);
System.out.println("My URL: " + URL);
System.out.println("My Header: " + authHeader);
assertThat("HTTP GET failed", response.getStatus(), is(Response.Status.OK.getStatusCode()));
}
}
Looks like the problem why 401 instead 200 is returned is LtpaToken2 Cookie which is not set in Test. Instead Header "Authorization: Bearer JWT_TOKEN" is used, but this doesn't work.
I Expect that Endpoint secured with "#RolesAllowed" should respond with 200 when header "Authorization: Bearer JWT_TOKEN" is provided. Are there some tricks that should be done with a cookie?
UPDATE 2019-05-23
This is the whole project.
Example test is located here. The failing test is ignored
#Test
public void getUserAndAccounts_withJwt_authorized() throws IOException {
Response response = HttpClientHelper.processRequest(URL, "GET", null, authHeader, null);
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
}
JWT token is created within following class in the #Before annotated method:
private String authHeader;
#Before
public void setup() throws Exception {
authHeader = "Bearer " + new JwtVerifier().createAdminJwt(USER_NAME1);
}
One thing to notice, that project is based on the following project.
Since the CWWKS1652A message was issued without a provider name, this indicates that appSecurity-3.0 is set and that at least a JSR-375 (a.k.a. Java EE Security API Specification) HttpAuthenticationMechanism is configured for the application, either via annotation or bean implementation. This causes an internal JASPIC provider to be created, therefore the null in the CWWKS1652A message, and this provider invokes the configured HttpAuthenticationMechanism that returns a AuthStatus.SEND_FAILURE status.
Please ensure that you intend to use an HttpAuthenticationMechanism and that valid authentication credentials are passed when challenged by this mechanism.
If it is determined that there is no HttpAuthenticationMechanism configured, then determine if there is an external JASPIC provider factory (AuthConfigFactory implementation) set via the authconfigprovider.factory property. In either case, it is the provider that responds with the AuthStatus.SEND_FAILURE seen in the message.

Authenticate from Retrofit with JWT token to Rest server

my server is Flask based, my client is android studio, and i'm communication using retrofit.
The problem is that i'm not able to pass the jwt token correctly from the android to the server after logging in.
With postman it's working good:
{{url}}/auth - I'm logging in as the user, and getting the JWT token.
Later i'm adding "Authorization" header, with the Value "JWT {{jwt_token}}" and
{{url}}/users/john - I'm asking for user info, which is recieved without problems.
The endpoint from android studio:
public interface RunnerUserEndPoints {
// #Headers("Authorization")
#GET("/users/{user}")
Call<RunnerUser> getUser(#Header("Authorization") String authHeader, #Path("user") String user);
The call itself (The access_token is correct before sending!):
final RunnerUserEndPoints apiService = APIClient.getClient().create(RunnerUserEndPoints.class);
Log.i("ACCESS","Going to send get request with access token: " + access_token);
Call<RunnerUser> call = apiService.getUser("JWT" + access_token, username);
Log.i("DEBUG","Got call at loadData");
call.enqueue(new Callback<RunnerUser>() {
#Override
public void onResponse(Call<RunnerUser> call, Response<RunnerUser> response) { ....
The response error log from the server:
File "C:\Users\Yonatan Bitton\RestfulEnv\lib\site-packages\flask_restful\__init__.py", line 595, in dispatch_request
resp = meth(*args, **kwargs)
File "C:\Users\Yonatan Bitton\RestfulEnv\lib\site-packages\flask_jwt\__init__.py", line 176, in decorator
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
File "C:\Users\Yonatan Bitton\RestfulEnv\lib\site-packages\flask_jwt\__init__.py", line 151, in _jwt_required
token = _jwt.request_callback()
File "C:\Users\Yonatan Bitton\RestfulEnv\lib\site-packages\flask_jwt\__init__.py", line 104, in _default_request_handler
raise JWTError('Invalid JWT header', 'Unsupported authorization type')
flask_jwt.JWTError: Invalid JWT header. Unsupported authorization type
10.0.0.6 - - [30/Sep/2017 01:46:11] "GET /users/john HTTP/1.1" 500 -
My api-client
public class APIClient {
public static final String BASE_URL = "http://10.0.0.2:8000";
private static Retrofit retrofit = null;
public static Retrofit getClient(){
if (retrofit==null){
retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
Log.i("DEBUG APIClient","CREATED CLIENT");
return retrofit;
}
}
Actually i'm really stuck. Tried to follow along all of the tutorials at retrofit's website without success.
I'm sure that there is a simple solution, I just need to add "Authorization" Header with Value "JWT " + access_token like it works in postman and that's it! Thanks.
EDIT:
The problem was the build of the access_token in my client.
I did:
JsonElement ans = response.body().get("access_token");
access_token = "JWT " + ans.toString();
Which I should have done:
JsonElement ans = response.body().get("access_token");
access_token = "JWT " + ans.getAsString();
So before it sent "JWT "ey..." " (Double "" )
And now it sends "JWT ey ... "
Let's start to look at what we know about the problem.
We know that the request is sent
We know that the server processes the request
We know that the JWT is invalid thanks to the error:
JWTError('Invalid JWT header', 'Unsupported authorization type')
If we look for that error in the flask_jwt source code, we can see that this is where our error is raised:
def _default_request_handler():
auth_header_value = request.headers.get('Authorization', None)
auth_header_prefix = current_app.config['JWT_AUTH_HEADER_PREFIX']
if not auth_header_value:
return
parts = auth_header_value.split()
if parts[0].lower() != auth_header_prefix.lower():
raise JWTError('Invalid JWT header', 'Unsupported authorization type')
elif len(parts) == 1:
raise JWTError('Invalid JWT header', 'Token missing')
elif len(parts) > 2:
raise JWTError('Invalid JWT header', 'Token contains spaces')
return parts[1]
Basically flask_jwt takes the Authorization header value and tries to split it into two. The function split can split a string by a delimiter, but if you call it without a delimiter it will use whitespace.
That tells us that flask_jwt expects a string that contains 2 parts separated by whitespace, such as space, and that the first part must match the prefix we are using (in this case JWT).
If we go back and look at your client code, we can see that when you are building the value to be put in the Authorization header you are not adding a space between JWT and the actual token:
apiService.getUser("JWT" + access_token, username);
This is what you should have been doing:
apiService.getUser("JWT " + access_token, username);
Notice the space after JWT?