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 private String _getError(Map<String, Object> info) 222 { 223 if (info.containsKey("error")) 224 { 225 return (String) info.get("error"); 226 } 227 else if (info.containsKey("message")) 228 { 229 return (String) info.get("message"); 230 } 231 else 232 { 233 return ""; 234 } 235 } 236 237 /** 238 * Creates a new private group. If the group already exists the operation will succeed. 239 * @param roomName name of the group 240 * @param create Create if necessary 241 * @return The group info. Can be null if no creation allowed 242 * @throws IOException something went wrong 243 */ 244 @SuppressWarnings("unchecked") 245 public Map<String, Object> getRoom(String roomName, boolean create) throws IOException 246 { 247 Map<String, Object> groupInfo = _doGet("v1/groups.info", Map.of("roomName", roomName)); 248 if (!_isOperationSuccessful(groupInfo)) 249 { 250 if (create) 251 { 252 groupInfo = _doPOST("v1/groups.create", Map.of("name", roomName)); 253 if (!_isOperationSuccessful(groupInfo)) 254 { 255 throw new IOException("Could not create room " + roomName + ", because " + _getError(groupInfo)); 256 } 257 } 258 else 259 { 260 return null; 261 } 262 } 263 return (Map<String, Object>) groupInfo.get("group"); 264 } 265 266 /** 267 * Remove a private group. If the group does not exist the operation will succeed. 268 * @param roomName name of the group 269 * @throws IOException something went wrong 270 */ 271 public void deleteRoom(String roomName) throws IOException 272 { 273 if (getRoom(roomName, false) != null) 274 { 275 Map<String, Object> deleteInfos = _doPOST("v1/groups.delete", Map.of("roomName", roomName)); 276 if (!_isOperationSuccessful(deleteInfos)) 277 { 278 throw new IOException("Could not delete room " + roomName + ", because " + _getError(deleteInfos)); 279 } 280 } 281 } 282 283 /** 284 * Get (or create) a new user. 285 * @param userIdentity the user that will be mirrored into chat 286 * @param create Create if missing 287 * @return the user info or null if user does not exist in chat and create was not required 288 * @throws IOException something went wrong 289 * @throws UserPreferencesException error while reading the user preferences 290 */ 291 @SuppressWarnings("unchecked") 292 public Map<String, Object> getUser(UserIdentity userIdentity, boolean create) throws IOException, UserPreferencesException 293 { 294 User user = _userManager.getUser(userIdentity); 295 if (user == null) 296 { 297 throw new IllegalStateException("Cannot create user in Rocket.Chat for unexisting user " + UserIdentity.userIdentityToString(userIdentity)); 298 } 299 300 Map<String, Object> userInfo = _doGet("v1/users.info", Map.of("username", userIdentitytoUserName(userIdentity))); 301 if (!_isOperationSuccessful(userInfo)) 302 { 303 if (create) 304 { 305 Map<String, String> ametysUserInfo = _getAmetysUserInfo(user); 306 String userName = ametysUserInfo.get("userName"); 307 String userEmail = ametysUserInfo.get("userEmail"); 308 309 userInfo = _doPOST("v1/users.create", Map.of("username", userIdentitytoUserName(userIdentity), 310 "email", userEmail, 311 "name", userName, 312 "verified", true, 313 "password", _getUserPassword(userIdentity))); 314 if (!_isOperationSuccessful(userInfo)) 315 { 316 throw new IllegalStateException("Cannot create user in Rocket.Chat for " + UserIdentity.userIdentityToString(userIdentity) + ": " + _getError(userInfo)); 317 } 318 319 getLogger().debug("User " + UserIdentity.userIdentityToString(userIdentity) + " created on the chat server"); 320 321 _updateAvatar(userIdentity); 322 } 323 else 324 { 325 return null; 326 } 327 } 328 329 Map<String, Object> userMap = (Map<String, Object>) userInfo.get("user"); 330 331 _userPreferencesManager.addUserPreference(userIdentity, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_ID, (String) userMap.get("_id")); 332 333 return userMap; 334 } 335 336 private Map<String, String> _getAmetysUserInfo(User user) 337 { 338 String userName = user.getFullName(); 339 String userEmail = user.getEmail(); 340 341 Content userContent = _userDirectoryHelper.getUserContent(user.getIdentity(), null); 342 if (userContent != null) 343 { 344 userName = StringUtils.defaultIfBlank(StringUtils.join(new String[] {userContent.getValue("firstname"), userContent.getValue("lastname")}, " "), userName); 345 userEmail = StringUtils.defaultIfBlank(userContent.getValue("email"), userEmail); 346 } 347 348 return Map.of("userName", userName, 349 "userEmail", userEmail); 350 } 351 352 /** 353 * Update user name, email, avatar... on chat server 354 * @param userIdentity The user to update 355 * @param changePassword Update the user password (slow) 356 * @throws UserPreferencesException If an error occurred while getting user infos 357 * @throws IOException If an error occurred while updating 358 * @throws InterruptedException If an error occurred while updating 359 */ 360 public void updateUserInfos(UserIdentity userIdentity, boolean changePassword) throws IOException, UserPreferencesException, InterruptedException 361 { 362 User user = _userManager.getUser(userIdentity); 363 if (user == null) 364 { 365 throw new IllegalStateException("Cannot update user in Rocket.Chat for unexisting user " + UserIdentity.userIdentityToString(userIdentity)); 366 } 367 368 Map<String, String> ametysUserInfo = _getAmetysUserInfo(user); 369 String userName = ametysUserInfo.get("userName"); 370 String userEmail = ametysUserInfo.get("userEmail"); 371 372 Map<String, String> data = new HashMap<>(); 373 data.put("email", userEmail); 374 data.put("name", userName); 375 if (changePassword) 376 { 377 data.put("password", _getUserPassword(userIdentity)); 378 } 379 380 Map<String, Object> updateInfos = _doPOST("v1/users.update", Map.of("userId", _getUserId(userIdentity), 381 "data", data)); 382 if (!_isOperationSuccessful(updateInfos)) 383 { 384 throw new IOException("Cannot update user " + UserIdentity.userIdentityToString(userIdentity) + " on chat server: " + _getError(updateInfos)); 385 } 386 387 if (changePassword) 388 { 389 // When changing password, it unlogs people and this takes time 390 Thread.sleep(1000); 391 } 392 393 _updateAvatar(userIdentity); 394 } 395 396 private void _updateAvatar(UserIdentity user) 397 { 398 ContextHelper.getRequest(_context).setAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_INTERNAL_ALLOWED, true); 399 400 Source src = null; 401 try 402 { 403 src = _sourceResolver.resolveURI("cocoon://_plugins/user-directory/user/" + user.getPopulationId() + "/" + user.getLogin() + "/image_64"); 404 try (InputStream is = src.getInputStream()) 405 { 406 Map<String, Object> avatarInfo = _doMultipartPOST("v1/users.setAvatar", Map.of("username", userIdentitytoUserName(user), 407 "image", is)); 408 if (!_isOperationSuccessful(avatarInfo)) 409 { 410 getLogger().warn("Fail to update avatar for user " + UserIdentity.userIdentityToString(user) + ": " + _getError(avatarInfo)); 411 } 412 } 413 } 414 catch (Exception e) 415 { 416 getLogger().warn("Fail to update avatar for user " + UserIdentity.userIdentityToString(user), e); 417 } 418 finally 419 { 420 _sourceResolver.release(src); 421 } 422 423 } 424 425 private boolean _isUserInGroup(UserIdentity userIdentity, String roomName) throws IOException 426 { 427 Map<String, Object> getMembers = _doGet("v1/groups.members", Map.of("roomName", roomName)); 428 429 if (_isOperationSuccessful(getMembers)) 430 { 431 @SuppressWarnings("unchecked") 432 List<Map<String, String>> membersObject = (List<Map<String, String>>) getMembers.get("members"); 433 for (Map<String, String> map : membersObject) 434 { 435 if (userIdentitytoUserName(userIdentity).equalsIgnoreCase(map.get("username"))) 436 { 437 return true; 438 } 439 } 440 } 441 442 return false; 443 } 444 445 /** 446 * Adds a user to the private group. 447 * @param userIdentity user to invite 448 * @param roomName name of the group where to invite the user 449 * @throws IOException something went wrong 450 * @throws UserPreferencesException If the user need to be created and its password cannot be stored 451 */ 452 public void addUserToRoom(UserIdentity userIdentity, String roomName) throws IOException, UserPreferencesException 453 { 454 if (!_isUserInGroup(userIdentity, roomName)) 455 { 456 Map<String, Object> userInfo = getUser(userIdentity, true); 457 Map<String, Object> groupInfo = getRoom(roomName, true); 458 459 String roomId = (String) groupInfo.get("_id"); 460 String userId = (String) userInfo.get("_id"); 461 462 Map<String, Object> inviteInfo = _doPOST("v1/groups.invite", Map.of("roomId", roomId, 463 "userId", userId)); 464 465 if (!_isOperationSuccessful(inviteInfo)) 466 { 467 throw new IOException("Could not add user " + UserIdentity.userIdentityToString(userIdentity) + " to room " + roomName + ": " + _getError(inviteInfo)); 468 } 469 } 470 } 471 472 /** 473 * Remove all existing user 474 * @param roomName The room name 475 * @param except Do not remove these users 476 * @throws IOException If an error occurred 477 */ 478 public void removeAllUsersFromRoom(String roomName, List<UserIdentity> except) throws IOException 479 { 480 List<String> exceptUsernames = except.stream().map(ChatHelper::userIdentitytoUserName).collect(Collectors.toList()); 481 482 Map<String, Object> groupInfo = getRoom(roomName, false); 483 String roomId = (String) groupInfo.get("_id"); 484 485 Map<String, Object> membersInfo = _doGet("v1/groups.members", Map.of("roomId", roomId)); 486 if (_isOperationSuccessful(membersInfo)) 487 { 488 @SuppressWarnings("unchecked") 489 List<Map<String, Object>> members = (List<Map<String, Object>>) membersInfo.get("members"); 490 for (Map<String, Object> member : members) 491 { 492 if (!exceptUsernames.contains(member.get("username"))) 493 { 494 _doPOST("v1/groups.kick", Map.of("roomId", roomId, 495 "userId", (String) member.get("_id"))); 496 } 497 } 498 } 499 } 500 501 /** 502 * Removes a user from the private group. 503 * @param user user to kick 504 * @param roomName name of the group from where to kick the user 505 * @return true if success 506 * @throws IOException something went wrong 507 * @throws UserPreferencesException If an error occurred with user password 508 */ 509 public boolean removeUserFromRoom(UserIdentity user, String roomName) throws IOException, UserPreferencesException 510 { 511 if (_isUserInGroup(user, roomName)) 512 { 513 Map<String, Object> userInfo = getUser(user, false); 514 Map<String, Object> groupInfo = getRoom(roomName, false); 515 516 if (userInfo != null && groupInfo != null) 517 { 518 String roomId = (String) groupInfo.get("_id"); 519 String userId = (String) userInfo.get("_id"); 520 521 return _isPOSTSucessful("v1/groups.kick", Map.of("roomId", roomId, 522 "userId", userId)); 523 } 524 else 525 { 526 return false; 527 } 528 } 529 530 return true; 531 } 532 533 /** 534 * Convert a useridentity to a chat server username 535 * @param userIdentity The user to convert 536 * @return the chat username 537 */ 538 public static String userIdentitytoUserName(UserIdentity userIdentity) 539 { 540 return UserIdentity.userIdentityToString(userIdentity).replaceAll("[@# ]", "_"); 541 } 542 543 private String _getUserPassword(UserIdentity userIdentity) throws UserPreferencesException 544 { 545 String cryptedPassword = _userPreferencesManager.getUserPreferenceAsString(userIdentity, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_PASSWORD); 546 if (!StringUtils.isBlank(cryptedPassword)) 547 { 548 try 549 { 550 return _cryptoHelper.decrypt(cryptedPassword); 551 } 552 catch (CryptoHelper.WrongKeyException e) 553 { 554 getLogger().warn("Password of user {} cannot be decrypted, and thus will be reset", UserIdentity.userIdentityToString(userIdentity), e); 555 } 556 } 557 558 return _generateAndStorePassword(userIdentity); 559 } 560 561 private String _getUserAuthToken(UserIdentity userIdentity) throws UserPreferencesException, IOException, InterruptedException 562 { 563 String cryptedToken = _userPreferencesManager.getUserPreferenceAsString(userIdentity, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_TOKEN); 564 if (!StringUtils.isBlank(cryptedToken)) 565 { 566 try 567 { 568 return _cryptoHelper.decrypt(cryptedToken); 569 } 570 catch (CryptoHelper.WrongKeyException e) 571 { 572 getLogger().warn("Token of user {} cannot be decrypted, and thus will be reset", UserIdentity.userIdentityToString(userIdentity), e); 573 } 574 } 575 return _generateAndStoreAuthToken(userIdentity, true); 576 } 577 578 579 private String _getUserId(UserIdentity userIdentity) throws UserPreferencesException 580 { 581 String userId = _userPreferencesManager.getUserPreferenceAsString(userIdentity, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_ID); 582 return userId; 583 } 584 585 private String _generateAndStorePassword(UserIdentity user) throws UserPreferencesException 586 { 587 Double random = Math.random(); 588 byte[] randoms = {random.byteValue()}; 589 String randomPassword = Sha2Crypt.sha256Crypt(randoms); 590 591 String cryptedPassword = _cryptoHelper.encrypt(randomPassword); 592 _userPreferencesManager.addUserPreference(user, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_PASSWORD, cryptedPassword); 593 594 return randomPassword; 595 } 596 597 598 @SuppressWarnings("unchecked") 599 private String _generateAndStoreAuthToken(UserIdentity user, boolean tryToChangePassword) throws IOException, UserPreferencesException, InterruptedException 600 { 601 Map<String, Object> loginInfo = _doPOST("v1/login", Map.of("user", userIdentitytoUserName(user), 602 "password", _getUserPassword(user))); 603 if (_isOperationSuccessful(loginInfo)) 604 { 605 String authToken = (String) ((Map<String, Object>) loginInfo.get("data")).get("authToken"); 606 String cryptedAuthToken = _cryptoHelper.encrypt(authToken); 607 _userPreferencesManager.addUserPreference(user, __USERPREF_CONTEXT, Collections.emptyMap(), __USERPREF_PREF_TOKEN, cryptedAuthToken); 608 609 return authToken; 610 } 611 else if (tryToChangePassword) 612 { 613 this.updateUserInfos(user, true); 614 615 return _generateAndStoreAuthToken(user, false); 616 } 617 else 618 { 619 throw new IOException("Could not log user " + UserIdentity.userIdentityToString(user) + " into chat " + _getError(loginInfo)); 620 } 621 } 622 623 private String _getUserStatus(UserIdentity user, String authToken, String userId) throws IOException 624 { 625 Map<String, Object> statusInfo = _doGet("v1/users.getStatus", Map.of("user", userIdentitytoUserName(user)), authToken, userId); 626 if (_isOperationSuccessful(statusInfo)) 627 { 628 return (String) statusInfo.get("status"); 629 } 630 else 631 { 632 return null; 633 } 634 } 635 636 /** 637 * Read the JSON result to test for success 638 * @param result the JSON result of a rest call 639 * @return true if success 640 */ 641 protected boolean _isOperationSuccessful(Map<String, Object> result) 642 { 643 Boolean success = false; 644 if (result != null) 645 { 646 Object successObj = result.get("success"); 647 if (successObj instanceof Boolean) 648 { 649 success = (Boolean) successObj; 650 } 651 else if (successObj instanceof String) 652 { 653 success = "true".equalsIgnoreCase((String) successObj); 654 } 655 else 656 { 657 Object statusObj = result.get("status"); 658 if (statusObj instanceof String) 659 { 660 success = "success".equalsIgnoreCase((String) statusObj); 661 } 662 } 663 } 664 return success; 665 } 666 667 /** 668 * Login the current user to the given room 669 * @param roomName The room to log in 670 * @return The info about the user 671 * @throws UserPreferencesException If the user password stored in prefs has an issue 672 * @throws IOException If an error occurred 673 * @throws InterruptedException If an error occurred 674 */ 675 @Callable 676 public Map<String, Object> login(String roomName) throws IOException, UserPreferencesException, InterruptedException 677 { 678 // Get current user in Ametys 679 UserIdentity user = _currentUserProvider.getUser(); 680 681 // Ensure user exists on chat server 682 getUser(user, true); 683 684 // Ensure the room exists on chat server 685 getRoom(roomName, true); 686 687 // Ensure the user is part of the room on chat server 688 addUserToRoom(user, roomName); 689 690 // Get the login info of the user 691 String authToken = _getUserAuthToken(user); 692 String userId = _getUserId(user); 693 694 // Ensure token validity by getting status on chat server 695 String status = _getUserStatus(user, authToken, userId); 696 if (status == null) 697 { 698 // If we cannot get status, this is probably because the auth token has expired or the user was recreated. Try a new one 699 authToken = _generateAndStoreAuthToken(user, true); 700 701 status = _getUserStatus(user, authToken, userId); 702 703 if (status == null) 704 { 705 throw new IllegalStateException("Cannot get the status of user " + UserIdentity.userIdentityToString(user)); 706 } 707 } 708 709 return Map.of("userId", userId, 710 "authToken", authToken, 711 "userName", userIdentitytoUserName(user), 712 "status", status); 713 } 714}