How to update keycloak user without keycloak admin client? - java

I'm writing custom REST endpoint.
When I call my Resource I trying to update some users in keycloak by getting instanse of UserResource class.
But after updating first one user transaction in KeycloakTransactionManager is closing and after what im getting exception java.lang.IllegalStateException: cannot access delegate without a transaction.
I tried to begin transaction by:
(for example)
KeycloakSession session = ... ; //got it from context
if(!session.getTransactionManager().isActive()){
session.getTransactionManager().begin();
}
But got "java.lang.IllegalStateException: Transaction in illegal state for rolleback: FINISHED" and after that "java.lang.IllegalStateException: No transaction associated with the current thread".
// custom resource class
public class MyResource {
#Context
private HttpHeaders headers;
private MyAdminAuth auth;
private MyHelper helper;
private AppAuthManager authManager;
// I've correctly getting session and with open/active transaction
private KeycloakSession session;
public MyResource(KeycloakSession session) {
this.authManager = new AppAuthManager();
this.session = session;
}
#Path("sync-users-run")
#POST
#NoCache
#ResponseObject
public String syncUsersRun(String json) {
Gson gson = new Gson();
helper = new MyHelper(authManager, session, headers);
SyncMain sync = new SyncMain(params, helper);
String response = gson.toJson(sync.run());
return response;
}
}
private boolean updateUserInKeycloak(UserRepresentation existingUser, User newData) {
existingUser.setEmail(newData.getEmail());
existingUser.setEnabled(newData.isActive());
existingUser.setFirstName(newData.getName());
existingUser.setLastName(newData.getLastName());
try {
KeycloakSession session = helper.getSession();
UserModel user = session.users().getUserById(existingUser.getId(), helper.getRealmFromSession(session));
UserResource userResource = helper.getInstanceOfUserResource(user, MyAdminAuth.ROLE_ALLOW_SYNC_USERS);
//trying to update user but it works only for the first one
Response response = userResource.updateUser(existingUser);
if (response.getStatus() != NO_CONTENT_CODE) {
return false;
}
} catch (IllegalStateException | WebApplicationException e) {
return false;
}
return true;
}
How can I update users without keycloak admin client corretly?
Maybe exist more easy and right way?

Related

Mongo Auth Provider doesn't inject user object in routing context after authentification in vert.x 4.2.5

i'm using vert.x 4.2.5, and mongodb as auth provider.
the problem that even after logging in successfully, the user still can't access to the private routes, and i can't find the user object injected in routing context
my api :
Router apiAuth = Router.router(vertx);
SessionStore store = LocalSessionStore.create(vertx);
SessionHandler sessionHandler = SessionHandler.create(store);
apiAuth.route().handler(sessionHandler);
apiAuth.route().handler(BodyHandler.create());
AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authenticationProvider);
apiAuth.post("/login").handler(this::login);
apiAuth.get("/checkLogin").handler(basicAuthHandler);
apiAuth.get("/checkLogin").handler(this::checkLogin);
Login method :
private void login(RoutingContext routingContext) {
JsonObject body = routingContext.getBodyAsJson();
JsonObject authInfo =
new JsonObject()
.put("username", body.getString("username"))
.put("password", body.getString("password"));
authenticationProvider
.authenticate(authInfo).onSuccess(user -> {
routingContext
.response()
.setStatusCode(200)
.putHeader("Content-Type", "application/json")
.end(
new JsonObject()
.put("success", true)
.encode());
});
}
checkLogin method :
private void checkLogin(RoutingContext routingContext) {
routingContext.response().setStatusCode(200).end(new JsonObject().put("authenticated", true).encode());
}
You need to use session for such kind of operations. When you put/add authenticated user data to routing context it's relevancy is only as long as this specific context/request is live, when the context ends the data added to it dies with it. So when you want to save your authenticated user data for next new request you need to save it in the session instance.
You can achieve this like this:
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.impl.UserImpl;
import io.vertx.ext.web.RoutingContext;
...
// in your authenticationProvider
public void authenticate(RoutingContext context, Handler<AsyncResult<User>> handler) {
.
your code to authenticate the user...
.
JsonObject userData = new JsonObject();
userData.put("id", 123456);
userData.put("name", "userName");
User user = new UserImpl(userData, new JsonObject());
context.session().put("user", user); // to add user data to the session
handler.handle(Future.succeededFuture(user));
}
private void checkLogin(RoutingContext context) {
User user = context.session().get("user"); // to get user data from the session
if (user == null) {
//user needs authentication
}
}

Android Login authentication with Spring boot server

I'm working on an android app where I need to login and register. I've created the server side using the java spring boot framework. Registration works well, but I cannot login. How can I solve? The code I wrote is as follows:
Server code for register:
#PostMapping("/register")
public Person addUser(#RequestBody Person user) {
user.setPassword(encoder.encode(user.getPassword()));
return userService.saveUser(user);
}
Server code for login:
#PostMapping("/login")
public UserDetails authenticate(#RequestBody Person principal) throws Exception {
return authService.authenticate(principal);
}
And this works well. Because if I try with postman, I can log well.
This is postman result
Also, registration with andorid works weel too. If I register an user from android, it'll be save into my db and with postman i can log with that user.
This is my db
This is my andorid code for registration:
class HttpRequestTask extends AsyncTask<Void, Void, Person> {
#Override
protected Person doInBackground(Void... params) {
try {
final String url = "myurl/register";
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
Person person= new Person();
person.setNome(myname);
person.setCognome(myusername);
person.setUsername(mynickname);
person.setPassword(mypassword);
person.setProfileType(myprofileType);
return restTemplate.postForObject(url, person, Person.class);
}
catch(Exception e){
Log.e("MainActivity", e.getMessage(), e);
}
return null;
}
}
And it works well.
This is my android code for login:
class HttpRequestTask extends AsyncTask<Void, Void, Person> {
#Override
protected Person doInBackground(Void... params) {
try {
final String url = "myurl/login";
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
Person person= new Person();
person.setUsername(myusername);
person.setPassword(mypassword);
return restTemplate.postForObject(url, person, Person.class);
}
catch(Exception e){
Log.e("MainActivity", e.getMessage(), e);
}
return null;
}
}
Login does not work.
Should I use Spring boot for android?
Does anyone have an example?
Thanks in Advance!.
I think you must send your auth token in the header. This sould be accomplished with an interceptor like this
public class AuthInterceptor implements ClientHttpRequestInterceptor {
public static final String TAG = "INTERCEPTOR";
#Bean
EgomniaLoginManager loginManager;
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
headers.set(ACCESS_TOKEN, loginManager.getAccessToken());
// logga request
if (BuildConfig.DEBUG) {
this.logRequest(request, body);
}
ClientHttpResponse response = execution.execute(request, body);
// logga response
if (BuildConfig.DEBUG) {
// this.logResponse(response);
// execute second time because logging consume response body
// response = execution.execute(request, body);
}
return response;
}
}
You must add this interceptor to your RestTemplate, I use Android Annotations to do that, so I don't remember how it could be done

How to get Session Id in Spring WebSocketStompClient?

How to get session id in Java Spring WebSocketStompClient?
I have WebSocketStompClient and StompSessionHandlerAdapter, which instances connect fine to websocket on my server. WebSocketStompClient use SockJsClient.
But I don't know how get session id of websocket connection. In the code with stomp session handler on client side
private class ProducerStompSessionHandler extends StompSessionHandlerAdapter {
...
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
...
}
stomp session contains session id, which is different from session id on the server.
So from this ids:
DEBUG ... Processing SockJS open frame in WebSocketClientSockJsSession[id='d6aaeacf90b84278b358528e7d96454a...
DEBUG ... DefaultStompSession - Connection established in session id=42e95c88-cbc9-642d-2ff9-e5c98fb85754
I need first session id, from WebSocketClientSockJsSession.
But I didn't found in WebSocketStompClient or SockJsClient any method to retrieve something like session id...
You can use #Header annotation to access sessionId:
#MessageMapping("/login")
public void login(#Header("simpSessionId") String sessionId) {
System.out.println(sessionId);
}
And it works fine for me without any custom interceptors
To get session id you need to define your own interceptor as below and set the session id as a custom attribute.
public class HttpHandshakeInterceptor implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession();
attributes.put("sessionId", session.getId());
}
return true;
}
Now you can get the same session id in the controller class.
#MessageMapping("/message")
public void processMessageFromClient(#Payload String message, SimpMessageHeaderAccessor headerAccessor) throws Exception {
String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
}
There is a way to extract sockjs sessionId via Reflection API:
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
// we need another sessionId!
System.out.println("New session established : " + session.getSessionId());
DefaultStompSession defaultStompSession =
(DefaultStompSession) session;
Field fieldConnection = ReflectionUtils.findField(DefaultStompSession.class, "connection");
fieldConnection.setAccessible(true);
String sockjsSessionId = "";
try {
TcpConnection<byte[]> connection = (TcpConnection<byte[]>) fieldConnection.get(defaultStompSession);
try {
Class adapter = Class.forName("org.springframework.web.socket.messaging.WebSocketStompClient$WebSocketTcpConnectionHandlerAdapter");
Field fieldSession = ReflectionUtils.findField(adapter, "session");
fieldSession.setAccessible(true);
WebSocketClientSockJsSession sockjsSession = (WebSocketClientSockJsSession) fieldSession.get(connection);
sockjsSessionId = sockjsSession.getId(); // gotcha!
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (StringUtils.isBlank(sockjsSessionId)) {
throw new IllegalStateException("couldn't extract sock.js session id");
}
String subscribeLink = "/topic/auth-user" + sockjsSessionId;
session.subscribe(subscribeLink, this);
System.out.println("Subscribed to " + subscribeLink);
String sendLink = "/app/user";
session.send(sendLink, getSampleMessage());
System.out.println("Message sent to websocket server");
}
Can be seen here: tutorial

HTPPSession with Google Cloud Endpoints new session every time

I am successfully using Google Cloud Endpoints. Now for custom user auth, I want to use HTTPSession. The problem is, the initial session is not being reused in future calls, and new session are created (I can see from datastore admin that the session all exists, _AH_SESSION entity). As instructed in the docs, i have enabled it in appengine-web.xml:
<sessions-enabled>true</sessions-enabled>
I made some sample code to narrow it down:
#Api(name = "loginService", version = "v0.1.5")
public class LoginService {
private static final Logger log = Logger.getLogger(LoginService.class.getName());
#ApiMethod(name = "login", path= "login")
public LoginResponse login(HttpServletRequest req)
{
HttpSession session = req.getSession(false);
if(session == null){
log.warning("creating new session");
session = req.getSession(true);
}
LoginResponse resp = new LoginResponse();
resp.statusCode = 200;
resp.statusMessage = "SessionId:" + session.getId();
return resp;
}
#ApiMethod(name = "show", path= "show")
public LoginResponse show(HttpServletRequest req)
{
//session should exist when calling LOGIN first
HttpSession session = req.getSession(false); //NULLPOINTER since session from login is not being reused/found!
LoginResponse resp = new LoginResponse();
resp.statusCode = 200;
resp.statusMessage = "SessionId:" + session.getId();
return resp;
}
public class LoginResponse implements Serializable{
public int statusCode;
public String statusMessage;
}
}`
So first, I call the login method, this creates a new session and prints me the session id. Then in the next request (both using Postman - which should track sessions - in Chrome as the API explorer) i call the 'show' endpoint, and there the previous session does not exist anymore, hence the nullpointer exception.
In the comments on this post, user mikO says endpoints don't keep the session. Is this the reason? I don't really understand the reason behind it. When I just deploy a 'regular' servlet on appengine, it DOES work using Postman or just browsing.. (testing by calling the getter twice, i see that the previous session is being picked up), so it seems that the comment in that post could be right, but i really don't understand why. Working code without endpoints:
public class LoginServlet extends HttpServlet {
private static final Logger log = Logger.getLogger(LoginServlet.class.getName());
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession s = request.getSession(false);
if (s == null) {
log.warning("creating new session");
s = request.getSession(true);
}
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>" + s.getId() + "</h1>");
}
}
Thanks!

Verify transaction on Google App Engine

I'm trying to get the transaction verification working for making an in-app purchase on Android in countries where Google Play isn't available.
When using the sandbox API everything seems to work well on my local Google App Engine dev server. The server sends a request to PayPal with the transaction ID and comes back with the JSON response containing the state of the transaction.
When I upload the code to Google App Engine, however, it instead uses the live API with corresponding credentials. When I do the transaction verification there, I get the following error from the Paypal REST SDK:
Response Code : 401 with response : {"error":"invalid_client","error_description":"The client credentials are invalid"}
In the HttpServlet I use this to set the constant DEBUG to true or false:
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
String dev = config.getServletContext().getServerInfo();
if (dev.contains("Development")) {
Constants.DEBUG = true;
} else {
Constants.DEBUG = false;
}
}
The transaction ID is verified with the code below. It gets the access token for making the API call and then verifies the transaction id. I also create a custom config which sets GOOGLE_APP_ENGINE to true and MODE to sandbox or live depending on the DEBUG constant (this is because I couldn't get the getServletContext().getResourceAsStream("sdk_config.properties") working as it gave an Access denied error).
public static String getAccessToken() throws PayPalRESTException {
String clientSecret, clientID;
if (Constants.DEBUG) {
clientSecret = Constants.CLIENT_SECRET_SANDBOX;
clientID = Constants.CLIENT_ID_SANDBOX;
} else {
clientSecret = Constants.CLIENT_SECRET_LIVE;
clientID = Constants.CLIENT_ID_LIVE;
}
return new OAuthTokenCredential(clientID, clientSecret,
getPaypalConfig()).getAccessToken();
}
public static Map<String, String> getPaypalConfig() {
Map<String, String> config = new HashMap<>();
config.put(com.paypal.core.Constants.GOOGLE_APP_ENGINE,
String.valueOf(true));
if (Constants.DEBUG) {
config.put(com.paypal.core.Constants.MODE,
com.paypal.core.Constants.SANDBOX);
} else {
config.put(com.paypal.core.Constants.MODE,
com.paypal.core.Constants.LIVE);
}
return config;
}
public static boolean payPalVerifier(String saleId)
throws PayPalRESTException {
if (accessToken == null) {
accessToken = Utils.getAccessToken();
apiContext = new APIContext(accessToken);
apiContext.setConfigurationMap(Utils.getPaypalConfig());
}
boolean completed = false;
Payment pay = Payment.get(apiContext, saleId);
for (Transaction transaction : pay.getTransactions()) {
for (RelatedResources relatedResources : transaction
.getRelatedResources()) {
if (com.pixplicity.Constants.DEBUG) {
completed = relatedResources.getSale().getState()
.equals("completed")
|| relatedResources.getSale().getState()
.equals("pending");
} else {
completed = relatedResources.getSale().getState()
.equals("completed");
}
}
}
return completed;
}
How do I solve this problem?

Categories