001/* 002 * Copyright 2021 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.workspaces.chat; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.nio.charset.StandardCharsets; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.component.Component; 029import org.apache.avalon.framework.context.Context; 030import org.apache.avalon.framework.context.ContextException; 031import org.apache.avalon.framework.context.Contextualizable; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.cocoon.components.ContextHelper; 036import org.apache.commons.codec.digest.Sha2Crypt; 037import org.apache.commons.lang3.StringUtils; 038import org.apache.excalibur.source.Source; 039import org.apache.excalibur.source.SourceResolver; 040import org.apache.http.Consts; 041import org.apache.http.HttpEntity; 042import org.apache.http.client.methods.CloseableHttpResponse; 043import org.apache.http.client.methods.HttpGet; 044import org.apache.http.client.methods.HttpPost; 045import org.apache.http.client.methods.HttpUriRequest; 046import org.apache.http.entity.ContentType; 047import org.apache.http.entity.StringEntity; 048import org.apache.http.entity.mime.HttpMultipartMode; 049import org.apache.http.entity.mime.MultipartEntityBuilder; 050import org.apache.http.impl.client.CloseableHttpClient; 051import org.apache.http.impl.client.HttpClients; 052import org.apache.http.util.EntityUtils; 053 054import org.ametys.cms.repository.Content; 055import org.ametys.core.authentication.AuthenticateAction; 056import org.ametys.core.ui.Callable; 057import org.ametys.core.user.CurrentUserProvider; 058import org.ametys.core.user.User; 059import org.ametys.core.user.UserIdentity; 060import org.ametys.core.user.UserManager; 061import org.ametys.core.userpref.UserPreferencesException; 062import org.ametys.core.userpref.UserPreferencesManager; 063import org.ametys.core.util.CryptoHelper; 064import org.ametys.core.util.JSONUtils; 065import org.ametys.core.util.URIUtils; 066import org.ametys.plugins.userdirectory.UserDirectoryHelper; 067import org.ametys.runtime.config.Config; 068import org.ametys.runtime.plugin.component.AbstractLogEnabled; 069 070/** 071 * Helper for chat 072 */ 073public class ChatHelper extends AbstractLogEnabled implements Component, Serviceable, Contextualizable 074{ 075 /** The Avalon role */ 076 public static final String ROLE = ChatHelper.class.getName(); 077 078 private static final String __USERPREF_PREF_PASSWORD = "workspaces.chat-connector-password"; 079 080 private static final String __USERPREF_PREF_TOKEN = "workspaces.chat-connector-token"; 081 082 private static final String __USERPREF_PREF_ID = "workspaces.chat-connector-id"; 083 084 private static final String __USERPREF_CONTEXT = "/workspaces.chat-connector"; 085 086 /** Rocket.Chat admin ID */ 087 private static final String CONFIG_ADMIN_ID = "workspaces.chat.rocket.admin.id"; 088 089 /** Rocket.Chat admin Token */ 090 private static final String CONFIG_ADMIN_TOKEN = "workspaces.chat.rocket.admin.token"; 091 092 /** Rocket.Chat URL */ 093 private static final String CONFIG_URL = "workspaces.chat.rocket.url"; 094 095 /** JSON Utils */ 096 protected JSONUtils _jsonUtils; 097 098 /** User Manager */ 099 protected UserManager _userManager; 100 101 /** User Preferences */ 102 protected UserPreferencesManager _userPreferencesManager; 103 104 /** Cryptography */ 105 protected CryptoHelper _cryptoHelper; 106 107 /** Current user provider */ 108 protected CurrentUserProvider _currentUserProvider; 109 110 private SourceResolver _sourceResolver; 111 112 private UserDirectoryHelper _userDirectoryHelper; 113 114 private Context _context; 115 116 public void contextualize(Context context) throws ContextException 117 { 118 _context = context; 119 } 120 121 public void service(ServiceManager manager) throws ServiceException 122 { 123 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 124 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 125 _userPreferencesManager = (UserPreferencesManager) manager.lookup(UserPreferencesManager.ROLE); 126 _cryptoHelper = (CryptoHelper) manager.lookup("org.ametys.plugins.workspaces.chat.cryptoHelper"); 127 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 128 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 129 _userDirectoryHelper = (UserDirectoryHelper) manager.lookup(UserDirectoryHelper.ROLE); 130 } 131 132 private Map<String, Object> _doGet(String api, Map<String, String> parameters) throws IOException 133 { 134 return _doGet(api, parameters, Config.getInstance().getValue(CONFIG_ADMIN_TOKEN), Config.getInstance().getValue(CONFIG_ADMIN_ID)); 135 } 136 137 private Map<String, Object> _doGet(String api, Map<String, String> parameters, String authToken, String userId) throws IOException 138 { 139 String path = Config.getInstance().getValue(CONFIG_URL) + "/api/" + api; 140 141 String uri = URIUtils.encodeURI(path, parameters); 142 HttpGet request = new HttpGet(uri); 143 request.setHeader("Content-Type", "application/json"); 144 145 return _execRequest(request, authToken, userId); 146 } 147 148 private Map<String, Object> _doPOST(String api, Map<String, Object> parameters) throws IOException 149 { 150 return _doPOST(api, parameters, Config.getInstance().getValue(CONFIG_ADMIN_TOKEN), Config.getInstance().getValue(CONFIG_ADMIN_ID)); 151 } 152 153 private Map<String, Object> _doPOST(String api, Map<String, Object> parameters, String authToken, String userId) throws IOException 154 { 155 String path = Config.getInstance().getValue(CONFIG_URL) + "/api/" + api; 156 157 HttpPost request = new HttpPost(path); 158 159 String json = _jsonUtils.convertObjectToJson(parameters); 160 request.setEntity(new StringEntity(json, ContentType.create("application/json", StandardCharsets.UTF_8))); 161 request.setHeader("Content-Type", "application/json"); 162 163 return _execRequest(request, authToken, userId); 164 } 165 166 private Map<String, Object> _doMultipartPOST(String api, Map<String, Object> parameters) throws IOException 167 { 168 return _doMultipartPOST(api, parameters, Config.getInstance().getValue(CONFIG_ADMIN_TOKEN), Config.getInstance().getValue(CONFIG_ADMIN_ID)); 169 } 170 171 private Map<String, Object> _doMultipartPOST(String api, Map<String, Object> parameters, String authToken, String userId) throws IOException 172 { 173 String path = Config.getInstance().getValue(CONFIG_URL) + "/api/" + api; 174 175 HttpPost request = new HttpPost(path); 176 177 MultipartEntityBuilder builder = MultipartEntityBuilder.create(); 178 builder.setMode(HttpMultipartMode.RFC6532); 179 180 for (Entry<String, Object> p : parameters.entrySet()) 181 { 182 if (p.getValue() instanceof String) 183 { 184 builder.addTextBody(p.getKey(), (String) p.getValue(), ContentType.create("text/plain", Consts.UTF_8)); 185 } 186 else if (p.getValue() instanceof InputStream) 187 { 188 builder.addBinaryBody(p.getKey(), (InputStream) p.getValue(), ContentType.create("image/unknown"), p.getKey()); 189 } 190 else 191 { 192 throw new UnsupportedOperationException("Cannot post the type " + p.getValue().getClass().getName() + " for parameter " + p.getKey()); 193 } 194 } 195 196 HttpEntity multipart = builder.build(); 197 request.setEntity(multipart); 198 199 return _execRequest(request, authToken, userId); 200 } 201 202 203 private boolean _isPOSTSucessful(String api, Map<String, Object> parameters) throws IOException 204 { 205 return _isOperationSuccessful(_doPOST(api, parameters)); 206 } 207 208 private Map<String, Object> _execRequest(HttpUriRequest request, String authToken, String userId) throws IOException 209 { 210 request.setHeader("X-Auth-Token", authToken); 211 request.setHeader("X-User-Id", userId); 212 213 try (CloseableHttpClient httpClient = HttpClients.createDefault(); 214 CloseableHttpResponse response = httpClient.execute(request)) 215 { 216 Map<String, Object> convertJsonToMap = _jsonUtils.convertJsonToMap(EntityUtils.toString(response.getEntity())); 217 return convertJsonToMap; 218 } 219 } 220 221 /** 222 * Creates a new private group. If the group already exists the operation will succeed. 223 * @param roomName name of the group 224 * @param create Create if necessary 225 * @return The group info. Can be null if no creation allowed 226 * @throws IOException something went wrong 227 */ 228 @SuppressWarnings("unchecked") 229 public Map<String, Object> getRoom(String roomName, boolean create) throws IOException 230 { 231 Map<String, Object> groupInfo = _doGet("v1/groups.info", Map.of("roomName", roomName)); 232 if (!_isOperationSuccessful(groupInfo)) 233 { 234 if (create) 235 { 236 groupInfo = _doPOST("v1/groups.create", Map.of("name", roomName)); 237 if (!_isOperationSuccessful(groupInfo)) 238 { 239 throw new IOException("Could not create room " + roomName + ", because " + groupInfo.get("error")); 240 } 241 } 242 else 243 { 244 return null; 245 } 246 } 247 return (Map<String, Object>) groupInfo.get("group"); 248 } 249 250 /** 251 * Remove a private group. If the group does not exist the operation will succeed. 252 * @param roomName name of the group 253 * @throws IOException something went wrong 254 */ 255 public void deleteRoom(String roomName) throws IOException 256 { 257 if (getRoom(roomName, false) != null) 258 { 259 Map<String, Object> deleteInfos = _doPOST("v1/groups.delete", Map.of("roomName", roomName)); 260 if (!_isOperationSuccessful(deleteInfos)) 261 { 262 throw new IOException("Could not delete room " + roomName + ", because " + deleteInfos.get("error")); 263 } 264 } 265 } 266 267 /** 268 * Get (or create) a new user. 269 * @param userIdentity the user that will be mirrored into chat 270 * @param create Create if missing 271 * @return the user info or null if user does not exist in chat and create was not required 272 * @throws IOException something went wrong 273 * @throws UserPreferencesException error while reading the user preferences 274 */ 275 @SuppressWarnings("unchecked") 276 public Map<String, Object> getUser(UserIdentity userIdentity, boolean create) throws IOException, UserPreferencesException 277 { 278 User user = _userManager.getUser(userIdentity); 279 if (user == null) 280 { 281 throw new IllegalStateException("Cannot create user in Rocket.Chat for unexisting user " + UserIdentity.userIdentityToString(userIdentity)); 282 } 283 284 Map<String, Object> userInfo = _doGet("v1/users.info", Map.of("username", userIdentitytoUserName(userIdentity))); 285 if (!_isOperationSuccessful(userInfo)) 286 { 287 if (create) 288 { 289 Map<String, String> ametysUserInfo = _getAmetysUserInfo(user); 290 String userName = ametysUserInfo.get("userName"); 291 String userEmail = ametysUserInfo.get("userEmail"); 292 293 userInfo = _doPOST("v1/users.create", Map.of("username", userIdentitytoUserName(userIdentity), 294 "email", userEmail, 295 "name", userName, 296 "verified", true, 297 "password", _getUserPassword(userIdentity))); 298 if (!_isOperationSuccessful(userInfo)) 299 { 300 throw new IllegalStateException("Cannot create user in Rocket.Chat for " + UserIdentity.userIdentityToString(userIdentity) + ": " + userInfo.get("error")); 301 } 302 303 getLogger().debug("User " + UserIdentity.userIdentityToString(userIdentity) + " created on the chat server"); 304 305 _updateAvatar(userIdentity); 306 } 307 else 308 { 309 return null; 310 } 311 } 312 313 Map<String, Object> userMap = (Map<String, Object>) userInfo.get("user"); 314 315 _userPreferencesManager.addUserPreference(userIdentity, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_ID, (String) userMap.get("_id")); 316 317 return userMap; 318 } 319 320 private Map<String, String> _getAmetysUserInfo(User user) 321 { 322 String userName = user.getFullName(); 323 String userEmail = user.getEmail(); 324 325 Content userContent = _userDirectoryHelper.getUserContent(user.getIdentity(), null); 326 if (userContent != null) 327 { 328 userName = StringUtils.defaultIfBlank(StringUtils.join(new String[] {userContent.getValue("firstname"), userContent.getValue("lastname")}, " "), userName); 329 userEmail = StringUtils.defaultIfBlank(userContent.getValue("email"), userEmail); 330 } 331 332 return Map.of("userName", userName, 333 "userEmail", userEmail); 334 } 335 336 /** 337 * Update user name, email, avatar... on chat server 338 * @param userIdentity The user to update 339 * @param changePassword Update the user password (slow) 340 * @throws UserPreferencesException If an error occurred while getting user infos 341 * @throws IOException If an error occurred while updating 342 * @throws InterruptedException If an error occurred while updating 343 */ 344 public void updateUserInfos(UserIdentity userIdentity, boolean changePassword) throws IOException, UserPreferencesException, InterruptedException 345 { 346 User user = _userManager.getUser(userIdentity); 347 if (user == null) 348 { 349 throw new IllegalStateException("Cannot update user in Rocket.Chat for unexisting user " + UserIdentity.userIdentityToString(userIdentity)); 350 } 351 352 Map<String, String> ametysUserInfo = _getAmetysUserInfo(user); 353 String userName = ametysUserInfo.get("userName"); 354 String userEmail = ametysUserInfo.get("userEmail"); 355 356 Map<String, String> data = new HashMap<>(); 357 data.put("email", userEmail); 358 data.put("name", userName); 359 if (changePassword) 360 { 361 data.put("password", _getUserPassword(userIdentity)); 362 } 363 364 Map<String, Object> updateInfos = _doPOST("v1/users.update", Map.of("userId", _getUserId(userIdentity), 365 "data", data)); 366 if (!_isOperationSuccessful(updateInfos)) 367 { 368 throw new IOException("Cannot update user " + UserIdentity.userIdentityToString(userIdentity) + " on chat server: " + updateInfos.get("error")); 369 } 370 371 if (changePassword) 372 { 373 // When changing password, it unlogs people and this takes time 374 Thread.sleep(1000); 375 } 376 377 _updateAvatar(userIdentity); 378 } 379 380 private void _updateAvatar(UserIdentity user) 381 { 382 ContextHelper.getRequest(_context).setAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_INTERNAL_ALLOWED, true); 383 384 Source src = null; 385 try 386 { 387 src = _sourceResolver.resolveURI("cocoon://_plugins/user-directory/user/" + user.getPopulationId() + "/" + user.getLogin() + "/image_64"); 388 try (InputStream is = src.getInputStream()) 389 { 390 Map<String, Object> avatarInfo = _doMultipartPOST("v1/users.setAvatar", Map.of("username", userIdentitytoUserName(user), 391 "image", is)); 392 if (!_isOperationSuccessful(avatarInfo)) 393 { 394 getLogger().warn("Fail to update avatar for user " + UserIdentity.userIdentityToString(user) + ": " + avatarInfo.get("error")); 395 } 396 } 397 } 398 catch (Exception e) 399 { 400 getLogger().warn("Fail to update avatar for user " + UserIdentity.userIdentityToString(user), e); 401 } 402 finally 403 { 404 _sourceResolver.release(src); 405 } 406 407 } 408 409 private boolean _isUserInGroup(UserIdentity userIdentity, String roomName) throws IOException 410 { 411 Map<String, Object> getMembers = _doGet("v1/groups.members", Map.of("roomName", roomName)); 412 413 if (_isOperationSuccessful(getMembers)) 414 { 415 @SuppressWarnings("unchecked") 416 List<Map<String, String>> membersObject = (List<Map<String, String>>) getMembers.get("members"); 417 for (Map<String, String> map : membersObject) 418 { 419 if (userIdentitytoUserName(userIdentity).equalsIgnoreCase(map.get("username"))) 420 { 421 return true; 422 } 423 } 424 } 425 426 return false; 427 } 428 429 /** 430 * Adds a user to the private group. 431 * @param userIdentity user to invite 432 * @param roomName name of the group where to invite the user 433 * @throws IOException something went wrong 434 * @throws UserPreferencesException If the user need to be created and its password cannot be stored 435 */ 436 public void addUserToRoom(UserIdentity userIdentity, String roomName) throws IOException, UserPreferencesException 437 { 438 if (!_isUserInGroup(userIdentity, roomName)) 439 { 440 Map<String, Object> userInfo = getUser(userIdentity, true); 441 Map<String, Object> groupInfo = getRoom(roomName, true); 442 443 String roomId = (String) groupInfo.get("_id"); 444 String userId = (String) userInfo.get("_id"); 445 446 Map<String, Object> inviteInfo = _doPOST("v1/groups.invite", Map.of("roomId", roomId, 447 "userId", userId)); 448 449 if (!_isOperationSuccessful(inviteInfo)) 450 { 451 throw new IOException("Could not add user " + UserIdentity.userIdentityToString(userIdentity) + " to room " + roomName + ": " + inviteInfo.get("error")); 452 } 453 } 454 } 455 456 /** 457 * Remove all existing user 458 * @param roomName The room name 459 * @param except Do not remove these users 460 * @throws IOException If an error occurred 461 */ 462 public void removeAllUsersFromRoom(String roomName, List<UserIdentity> except) throws IOException 463 { 464 List<String> exceptUsernames = except.stream().map(ChatHelper::userIdentitytoUserName).collect(Collectors.toList()); 465 466 Map<String, Object> groupInfo = getRoom(roomName, false); 467 String roomId = (String) groupInfo.get("_id"); 468 469 Map<String, Object> membersInfo = _doGet("v1/groups.members", Map.of("roomId", roomId)); 470 if (_isOperationSuccessful(membersInfo)) 471 { 472 @SuppressWarnings("unchecked") 473 List<Map<String, Object>> members = (List<Map<String, Object>>) membersInfo.get("members"); 474 for (Map<String, Object> member : members) 475 { 476 if (!exceptUsernames.contains(member.get("username"))) 477 { 478 _doPOST("v1/groups.kick", Map.of("roomId", roomId, 479 "userId", (String) member.get("_id"))); 480 } 481 } 482 } 483 } 484 485 /** 486 * Removes a user from the private group. 487 * @param user user to kick 488 * @param roomName name of the group from where to kick the user 489 * @return true if success 490 * @throws IOException something went wrong 491 * @throws UserPreferencesException If an error occurred with user password 492 */ 493 public boolean removeUserFromRoom(UserIdentity user, String roomName) throws IOException, UserPreferencesException 494 { 495 if (_isUserInGroup(user, roomName)) 496 { 497 Map<String, Object> userInfo = getUser(user, false); 498 Map<String, Object> groupInfo = getRoom(roomName, false); 499 500 if (userInfo != null && groupInfo != null) 501 { 502 String roomId = (String) groupInfo.get("_id"); 503 String userId = (String) userInfo.get("_id"); 504 505 return _isPOSTSucessful("v1/groups.kick", Map.of("roomId", roomId, 506 "userId", userId)); 507 } 508 else 509 { 510 return false; 511 } 512 } 513 514 return true; 515 } 516 517 /** 518 * Convert a useridentity to a chat server username 519 * @param userIdentity The user to convert 520 * @return the chat username 521 */ 522 public static String userIdentitytoUserName(UserIdentity userIdentity) 523 { 524 return UserIdentity.userIdentityToString(userIdentity).replaceAll("@", "_").replaceAll("#", "_"); 525 } 526 527 private String _getUserPassword(UserIdentity userIdentity) throws UserPreferencesException 528 { 529 String cryptedPassword = _userPreferencesManager.getUserPreferenceAsString(userIdentity, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_PASSWORD); 530 if (!StringUtils.isBlank(cryptedPassword)) 531 { 532 try 533 { 534 return _cryptoHelper.decrypt(cryptedPassword); 535 } 536 catch (CryptoHelper.WrongKeyException e) 537 { 538 getLogger().warn("Password of user {} cannot be decrypted, and thus will be reset", UserIdentity.userIdentityToString(userIdentity), e); 539 } 540 } 541 542 return _generateAndStorePassword(userIdentity); 543 } 544 545 private String _getUserAuthToken(UserIdentity userIdentity) throws UserPreferencesException, IOException, InterruptedException 546 { 547 String cryptedToken = _userPreferencesManager.getUserPreferenceAsString(userIdentity, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_TOKEN); 548 if (!StringUtils.isBlank(cryptedToken)) 549 { 550 try 551 { 552 return _cryptoHelper.decrypt(cryptedToken); 553 } 554 catch (CryptoHelper.WrongKeyException e) 555 { 556 getLogger().warn("Token of user {} cannot be decrypted, and thus will be reset", UserIdentity.userIdentityToString(userIdentity), e); 557 } 558 } 559 return _generateAndStoreAuthToken(userIdentity, true); 560 } 561 562 563 private String _getUserId(UserIdentity userIdentity) throws UserPreferencesException 564 { 565 String userId = _userPreferencesManager.getUserPreferenceAsString(userIdentity, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_ID); 566 return userId; 567 } 568 569 private String _generateAndStorePassword(UserIdentity user) throws UserPreferencesException 570 { 571 Double random = Math.random(); 572 byte[] randoms = {random.byteValue()}; 573 String randomPassword = Sha2Crypt.sha256Crypt(randoms); 574 575 String cryptedPassword = _cryptoHelper.encrypt(randomPassword); 576 _userPreferencesManager.addUserPreference(user, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_PASSWORD, cryptedPassword); 577 578 return randomPassword; 579 } 580 581 582 @SuppressWarnings("unchecked") 583 private String _generateAndStoreAuthToken(UserIdentity user, boolean tryToChangePassword) throws IOException, UserPreferencesException, InterruptedException 584 { 585 Map<String, Object> loginInfo = _doPOST("v1/login", Map.of("user", userIdentitytoUserName(user), 586 "password", _getUserPassword(user))); 587 if (_isOperationSuccessful(loginInfo)) 588 { 589 String authToken = (String) ((Map<String, Object>) loginInfo.get("data")).get("authToken"); 590 String cryptedAuthToken = _cryptoHelper.encrypt(authToken); 591 _userPreferencesManager.addUserPreference(user, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_TOKEN, cryptedAuthToken); 592 593 return authToken; 594 } 595 else if (tryToChangePassword) 596 { 597 this.updateUserInfos(user, true); 598 599 return _generateAndStoreAuthToken(user, false); 600 } 601 else 602 { 603 throw new IOException("Could not log user " + UserIdentity.userIdentityToString(user) + " into chat " + loginInfo.get("error")); 604 } 605 } 606 607 private String _getUserStatus(UserIdentity user, String authToken, String userId) throws IOException 608 { 609 Map<String, Object> statusInfo = _doGet("v1/users.getStatus", Map.of("user", userIdentitytoUserName(user)), authToken, userId); 610 if (_isOperationSuccessful(statusInfo)) 611 { 612 return (String) statusInfo.get("status"); 613 } 614 else 615 { 616 return null; 617 } 618 } 619 620 /** 621 * Read the JSON result to test for success 622 * @param result the JSON result of a rest call 623 * @return true if success 624 */ 625 protected boolean _isOperationSuccessful(Map<String, Object> result) 626 { 627 Boolean success = false; 628 if (result != null) 629 { 630 Object successObj = result.get("success"); 631 if (successObj instanceof Boolean) 632 { 633 success = (Boolean) successObj; 634 } 635 else if (successObj instanceof String) 636 { 637 success = "true".equalsIgnoreCase((String) successObj); 638 } 639 else 640 { 641 Object statusObj = result.get("status"); 642 if (statusObj instanceof String) 643 { 644 success = "success".equalsIgnoreCase((String) statusObj); 645 } 646 } 647 } 648 return success; 649 } 650 651 /** 652 * Login the current user to the given room 653 * @param roomName The room to log in 654 * @return The info about the user 655 * @throws UserPreferencesException If the user password stored in prefs has an issue 656 * @throws IOException If an error occurred 657 * @throws InterruptedException If an error occurred 658 */ 659 @Callable 660 public Map<String, Object> login(String roomName) throws IOException, UserPreferencesException, InterruptedException 661 { 662 // Get current user in Ametys 663 UserIdentity user = _currentUserProvider.getUser(); 664 665 // Ensure user exists on chat server 666 getUser(user, true); 667 668 // Ensure the room exists on chat server 669 getRoom(roomName, true); 670 671 // Ensure the user is part of the room on chat server 672 addUserToRoom(user, roomName); 673 674 // Get the login info of the user 675 String authToken = _getUserAuthToken(user); 676 String userId = _getUserId(user); 677 678 // Ensure token validity by getting status on chat server 679 String status = _getUserStatus(user, authToken, userId); 680 if (status == null) 681 { 682 // If we cannot get status, this is probably because the auth token has expired or the user was recreated. Try a new one 683 authToken = _generateAndStoreAuthToken(user, true); 684 685 status = _getUserStatus(user, authToken, userId); 686 687 if (status == null) 688 { 689 throw new IllegalStateException("Cannot get the status of user " + UserIdentity.userIdentityToString(user)); 690 } 691 } 692 693 return Map.of("userId", userId, 694 "authToken", authToken, 695 "userName", userIdentitytoUserName(user), 696 "status", status); 697 } 698}