My client is using the fetch api to interact with a Controller method that will decrypt a password and send the decrypted password back to the client as plain text. However, the response body never contains the password. It also continues to set the content type as basic even though I am setting it to text/plain. What am I doing wrong?
Client
function showCredentialModal(credentialId, url, username, password) {
$('#credential-id').val(credentialId ? credentialId : '');
$('#credential-url').val(url ? url : '');
$('#credential-username').val(username ? username : '');
// Display encrypted password if the current user is not the owner of the credential
if (password) {
fetch('/credentials/decrypt?credentialId=' + credentialId)
.then(response =>
console.log(response))
.catch(() =>
$('#credential-password').val('error'));
} else {
$('#credential-password').val('');
}
$('#credentialModal').modal('show');
}
Controller
#GetMapping("/decrypt")
public void doGet(HttpServletResponse response,Authentication authentication,
#ModelAttribute Credential credential) throws IOException {
User user = getCurrentUser(authentication);
credential = credentialService.getCredential(credential.getCredentialId());
boolean result = validationService.validate(credential, user);
if (result) {
String decryptedPassword = credentialService.decryptPassword(credential);
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
try (PrintWriter out = response.getWriter()) {
out.print(decryptedPassword);
out.flush();
}
}
}
Response:
Response {type: "basic", url: "http://localhost:8080/credentials/decrypt?credentialId=1", redirected: false, status: 200, ok: true, …}
body: ReadableStream
locked: false
__proto__: ReadableStream
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: ""
type: "basic"
url: "http://localhost:8080/credentials/decrypt?credentialId=1"
__proto__: Response
Your client just prints the response to console:
.then(response => console.log(response))
which shows a response with a ReadableStream body that isn't consumed:
body: ReadableStream
...
bodyUsed: false
You need to read the content of that stream to get the content the servlet returned. See for example:
Using readable streams
Body mixin
Right now you are just dumping the Response to console and its string representation isn't what you expect it to be (i.e. it's not the content but a wrapper for it). Your servlet code seems fine, it's your JavaScript client needs to be modified to read the content from within the response.
Try to debug. Actually what is wrote over very difficult to understand. Here could be some cases, but sure that "result" returns always false.
Some question for debugging:
Could method getCurrentUser() be consumed with null?
credentialId; It consumed from URL parameters that you pass in fetch method.
My suggestion to rewrite this code using samples in Spring Documentation.
Now it looks like you copied some snippets from different guides.
Related
I am using swagger codegen to generate the controller for me. And there is a if statement there but it seems to me it is not necessary. Thank you.
String accept = request.getHeader("Accept");
if (accept != null && accept.contains("application/json"))
Your swagger document must have something like below to API method:
responses:
"200":
description: OK
content:
application/json:
As per this, the response of your API is of type application/json:. But additionally in future, if the server decides to produce some other type of response also as below:
responses:
"200":
content:
image/svg+xml:
schema:
type: string
application/json:
schema:
$ref: "#/components/schemas/MyDto"
In this case to decide the response type in response Accept parameter would be necessary. So in my opinion there are 2 reasons for this condition to generate:
That Client and Server are in same contract for returning content.
If tomorrow new content type is added old code is not breaking.
Content-Type: it is a header that tells the format of data sent in HTTP messages (both requests and responses).
Accept: it is a header that is placed in a request when requested from a browser (client) to a web server.
This Accept header simply means that I(client) will only allow you this data type as a response.
If the server supports multiple data types, The server determines a data type of response using a accept header like this,
#GetMapping("/someresources")
public ResponseEntity<String> getSomeresources(#RequestHeader("accept") String accept) {
List<SomeResource> someresources = someService.someresources();
//for react app
if(accept.contains("application/json")) {
SomeresourcesRepresentation representation = new SomeresourcesRepresentation(someresources);
String serialziedRepresentaiton = jsonSerializer.serialize(representation);
ResponseEntity<String> response = ResponseEntity
.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(serialziedRepresentaiton);
return response;
}
//for web browser
if(accept.contains("text/html")) {
String html = "<!doctype html>"
+ "<html>"
+ "<head>"
+ "<meta charset='UTF-8'>"
+ "<title>summary</title>"
+ "</head>"
+ "<body>"
+ "someresources size : "+someresources.size()
+ "</body>"
+ "</html>";
ResponseEntity<String> response = ResponseEntity
.status(HttpStatus.OK)
.contentType(MediaType.TEXT_HTML)
.body(html);
return response;
}
return ResponseEntity.notFound().build();
}
I was able to create user in Keycloak by posting a json containing only 1 user using postman.
http://localhost:8080/auth/admin/realms/master/users
But when i tried to create more than 1 user by passing a json array of more than 1 record i am getting a 500 Internal server error
[
{
"username": "user1",
"firstName": "John",
"attributes": {
"pl_uid": null
},
"credentials": [
{
"temporary": true,
"type": "password",
"value": "ares2012"
}
]
},
{
"username": "1000195",
"firstName": "Matt",
"attributes": {
"pl_uid": null
},
"credentials": [
{
"temporary": true,
"type": "password",
"value": "rx3o0t9f"
}
]
}
]
Is there any way by which we can send a json array to keycloak and create users there?
Please have a look on the discussion added in Keycloak mailing list
Just double checked the approach I suggested. I thought we had made
it possible to import users into an existing realm, but that's not the
case. You have to create the whole realm. It's still possible to do it
this way, first create the realm and add an example user. Stop the
server and run it again with:
bin/standalone.sh -Dkeycloak.migration.action=export -Dkeycloak.migration.provider=dir -Dkeycloak.migration.realmName=<realm name> -Dkeycloak.migration.dir=<dir name>
Replace realm name and dir name
In dir name you should then get a few json files. You can then
update realm name-users-0.json to add the users you want to import.
As Bill points out the admin client could be a good alternative
approach. We also have a Java client that makes it simpler to use.
Have a look at the admin-client example.
So this URL can help. Have a look at this link
Another option is using partialImport API for importing users (use admin user token):
access_token=`curl --data "grant_type=password&username=admin&password=admin&client_secret=secret&client_id=admin-cli" http://localhost:8180/auth/realms/master/protocol/openid-connect/token| jq -r .access_token`
curl -X POST -H 'Accept: application/json' -H 'Content-Type: application/json' -H "Authorization: Bearer $access_token" --data "#$PWD/myrealm-users-0.json" http://localhost:8180/auth/admin/realms/myrealm/partialImport
After checking Keycloak's REST API doesn't look like bulk/batch requests for creating users are accepted. The only solution would be to send the POST request for every user.
This is not surprising, HTTP is not prepared for this kind of requests:
As HTTP does not provide proper guidance for handling batch/bulk requests and responses.
from this RESTful guide. Have a look to it, it's really usefull in REST devlopments.
I checked the paritalImport approach as suggested by others, it seems not available anymore in later versions such as 19.0.1. The error message is something like {"error":"RESTEASY003210: Could not find resource for full path: localhost:8080/auth/admin/realms/myrealm/partialImport"}%.
So I invoke Keycloak API to create users, one by one though. But as long as we can use a program to do it, it's also acceptable.
For example, we can write some java code to invoke the creating user API:
public String createUser(UserPayload user) throws IOException {
var url = "https://keycloak.jiwai.win/auth/admin/realms/UniHeart/users";
var payload = java.lang.String.format("{\"firstName\":\"Sergey\",\"lastName\":\"Kargopolov\", \"email\":\"%s\", \"enabled\":\"true\", \"username\":\"%s\", \"credentials\":[{\"type\":\"password\",\"value\":\"%s\",\"temporary\":false}]}", user.getEmail(), user.getUsername(), user.getPassword());
return this.jsonRequest(url, payload).toString();
}
The above code is merely calling the create user API. But to call it successfully we need firstly got the admin access token:
public KeycloakAccessTokenPayload getAdminAccessToken() throws IOException {
var username = System.getenv("KC_ADMIN");
var password = System.getenv("KC_PASSWORD");
System.out.println(java.lang.String.format("username = %s; password = %s", username, password));
var mediaType = MediaType.parse("application/x-www-form-urlencoded");
var body = RequestBody.create(mediaType, java.lang.String.format("username=%s&password=%s&grant_type=password&client_id=admin-cli", username, password));
var request = new Request.Builder()
.url("https://keycloak.jiwai.win/auth/realms/master/protocol/openid-connect/token")
.method("POST", body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.build();
var response = client.newCall(request).execute();
var s = Objects.requireNonNull(response.body()).string();
System.out.println(java.lang.String.format("token response = %s", s));
return JsonHelper.parseFrom(s);
}
And then pass the admin access token to the target API by setting the HTTP header:
public Response jsonRequest(String url, String payload) throws IOException {
var mediaType = MediaType.parse("application/json");
var body = RequestBody.create(mediaType, payload);
var request = new Request.Builder().url(url).method("POST", body).addHeader("Content-Type", "application/json").addHeader("Authorization", java.lang.String.format("Bearer %s", getAdminAccessToken().access_token)).build();
var res = client.newCall(request).execute();
System.out.println(java.lang.String.format("jsonRequest response = %s", res.toString()));
return res;
}
The fully working example is here: https://github.com/Jeff-Tian/keycloak-springboot/blob/main/src/main/java/com/example/keycloakspringboot/KeycloakHelper.java#L29
please, this is y concern: I'll like to know how to query a web service defined as in the below code using postman for test purposes.
PS: I can't change the method signature
I have a web service like this :
#POST
#Path("/uploadOpenAccountRequestFiles/{requestId}")
#Consumes({MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA})
public Response uploadOpenAccountRequestFiles(#PathParam("requestId") String requestId,
MultipartFormDataInput multipartFormDataInput)
throws JsonGenerationException, JsonMappingException, IOException {
initContext();
logger.info("DIGIBANK : uploadOpenAccountRequestFiles END: ");
String msge = "";
try {
digiBean.saveToserver(requestId, multipartFormDataInput);
} catch (Exception e) {
msge = e.getMessage();
e.printStackTrace();
}
org.af.webservice.Response resp = new org.af.webservice.Response(
request.getSession().getId(), "uploadOpenAccountRequestFiles", "",
msge.equalsIgnoreCase("OK") ? true : false, msge.equalsIgnoreCase("OK") ? false : true, "",
msge.equalsIgnoreCase("OK") ? true : false, "Boolean", msge);
logger.info("DIGIBANK : uploadOpenAccountRequestFiles END: ");
return Response.ok().entity(mapper.writeValueAsString(resp)).build();
}
this is are images of my configurations:
enter image description here
enter image description here
For calling that service, you need to pass requestId like below:
http://localhost:8080/uploadOpenAccountRequestFiles/requestId-value-here
For sending MultiPart data such as a file, you need to select form-data option in the body section in Postman and select the file by selecting the File dropdown. Also, make sure to set the appropriate headers for the request.
Check the below stackoverflow answer for more details:
Tool for sending multipart/form-data request
The steps of uploading a file through postman along with passing some input data along with the multipart request is very well discussed in below blog along with the screenshot. In this blog, the api code is written in node js. You can go through it once. It may give some information.
https://jksnu.blogspot.com/2021/09/how-to-create-post-request-with.html
I've searched around here as well as elsewhere online and can't seem to find the answer for what I think is a simple error on my part. Basically I want to transfer a file from one machine to another by issuing a Python requests.POST request to a Java REST interface on the remote machine. The Java side looks like this:
#ApiOperation(value = "Binary file transfer", nickname = "Binary file transfer")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Success", response = HttpMessageInformationReturnDataBean.class),
#ApiResponse(code = 404, message = "Not Found")})
#RequestMapping(value = "/vm/{version}/uploadbinfile", method = RequestMethod.POST)
public String handleFileUpload(#RequestParam("binaryFile") MultipartFile file) {
if (!file.isEmpty())
{
try
{ ... the code that handles the transfer
On the Python side, the method looks like this:
def xfer_trm_binaries(self):
params = {"file": ('binaryFile',os.path.basename('TRMServer.jar')),
"folder": os.path.dirname(self.dest_temp_path),
"submit": "Submit"}
url = self.form_url("/vm/v1/uploadbinfile", self.trm_server_ip_address, self.vrm_server_port)
header=self.form_header(self.vrm_key)
header['Content-Type'] = 'multipart/file-data; boundary=randomboundarysequence'
header['enctype'] = "multipart/file-data"
print 'Send :' + url
binfile = self.local_jar_path+'TRMServer.jar'
with open(binfile, 'rb') as mfile:
try:
result = requests.post(url, headers=header,
data=params, files={'file': mfile}, verify=False)
except Exception:
The header that gets assembled there looks like this:
{'Content-Type': 'multipart/file-data; boundary=randomboundarysequence', 'Accept': 'application/json', 'Authorization': u'Bearer 8b2b6e53-9008-44b7-9d34-b5ecb9659250', 'enctype': 'multipart/file-data'}
The request is sent, however the response is always a 400 error, because it complains the MultipartFile parameter 'binaryFile' is missing:
'{"timestamp":1488597880207,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MissingServletRequestParameterException","message":"Required MultipartFile parameter \\'binaryFile\\' is not present","path":"/vm/v1/uploadbinfile"}'
I've tried adding a 'name' value to both the params and headers of the request but it always comes back with the 400 code. Does anyone out there know what I might be doing wrong?
Actually I eventually figured this out - basically I had a method that formed the header to include the oauth bearer token, along with the ContentType and AcceptType...I then overwrote those with the multipart file info. THAT was what the receiving REST interface didn't like. When I just eliminated those header attributes altogether, it seemed to figure it out on its own.
I have a function that is called when a button is clicked, this function sends an ajax request using jquery. On success I have some logical if and else if statements. The web server can only send 2 types of text responses. It can either be "Success" or "Error". But when testing for these two conditions they seem to fail. I have added an else statement and an alert to tell me what the server is sending but just as I expected, it is either "Success" or "Error", the way I programmed my servlet in the server, it can only send "Success" or "Error" as response. Moreover my alert is spitting out "Success" or "Error" please I dont understand what is wrong here. please help.
function deleteRecord(productID, description)
{
var errorMessage = '<td class="red-left">There was an error. Please try again.</td>';
var successMessage = '<td class="green-left">Product '+productID+' ('+description+') has been deleted sucessfully.</td>';
var data = "productID="+productID+"&description="+description+"&deleteProduct=true";
$.ajax({
type: "GET",
url: "deleteInventoryRecord.mpcs",
data: data,
cache: false,
success: function(info)
{
if(info == 'Success')//This does not work
{
$(".green-left").replaceWith(successMessage);
$("#message-green").fadeIn("slow");
$("#product-"+productID).remove();
}
else if(info == 'Error')//This does not work
{
$(".red-left").replaceWith(errorMessage);
$("#message-red").fadeIn("slow");
}
else//This works always but I dont need it
{
alert(info); //It always says "Success" or "Error"
}
},
dataType: 'text'
});
}
Here is the servlet code that sends the response:
private void deleteProduct(HttpServletResponse response, String productID) throws IOException
{
try
{
response.setContentType("text/html");
InventoryDAO iDao = new InventoryDAO();
if(iDao.deleteProduct(productID) == true)
response.getWriter().println("Success");
else
throw new RuntimeException("Error");
}
catch(ClassNotFoundException | IOException | SQLException | RuntimeException xcp)
{
response.getWriter().println("Error");
}
}
Going to take a shot in the dark here, and say that the server is sending the response in UTF8 with a BOM, and this is causing your comparisons to fail.
To confirm, try alert(info.length). It should be 7 for "Success" or 5 for "Error". If it is not, then you probably have a BOM.
Another thing to check of course is that there is no whitespace in the response - again, the length check will help verify this.
You can fix this by encoding your server-side scripts as "UTF8 without BOM", sometimes referred to as "ANSI as UTF8" depending on your editor. Alternatively, change your if blocks to:
if( info.match(/Success$/)) ...
else if( info.match(/Error$/)) ...
Your deleteProduct method sets content type "text/html", but you are sending plain text. This is likely to confuse jQuery, as it tries to guess the type of info based on the content type header sent. Either use "text/plain", or even better "application/json", so you may sent more info to the client.