diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutApp2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutApp2x.scala index 602da44b46ae..14bbc3c5e555 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutApp2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutApp2x.scala @@ -71,8 +71,7 @@ object BreakoutRoomsUtil extends SystemConfiguration { breakoutMeetingId: String, avatarURL: String, webcamBackgroundURL: String, - role: String, - password: String + role: String ): (collection.immutable.Map[String, String], collection.immutable.Map[String, String]) = { val moderator = role == "MODERATOR" val params = collection.immutable.HashMap( @@ -83,7 +82,7 @@ object BreakoutRoomsUtil extends SystemConfiguration { "avatarURL" -> urlEncode(avatarURL), "webcamBackgroundURL" -> urlEncode(webcamBackgroundURL), "userdata-bbb_parent_room_moderator" -> urlEncode(moderator.toString()), - "password" -> urlEncode(password), + "role" -> urlEncode(role), "redirect" -> urlEncode("true") ) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutHdlrHelpers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutHdlrHelpers.scala index 0ccef0fab785..bf3446f8face 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutHdlrHelpers.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutHdlrHelpers.scala @@ -5,7 +5,7 @@ import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.core.api.{ BreakoutRoomUsersUpdateInternalMsg } import org.bigbluebutton.core.bus.{ BigBlueButtonEvent, InternalEventBus } import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser } -import org.bigbluebutton.core.models.{ Roles, Users2x, VoiceUsers } +import org.bigbluebutton.core.models.{ Users2x, VoiceUsers } import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } object BreakoutHdlrHelpers extends SystemConfiguration { @@ -41,8 +41,8 @@ object BreakoutHdlrHelpers extends SystemConfiguration { for { user <- Users2x.findWithIntId(liveMeeting.users2x, userId) apiCall = "join" - password = if (user.role == Roles.MODERATOR_ROLE) liveMeeting.props.password.moderatorPass - else liveMeeting.props.password.viewerPass + // The breakout room is joined using the user's role (MODERATOR/VIEWER); + // the legacy password-based join was removed in BigBlueButton 4.0. (redirectParams, redirectToHtml5Params) = BreakoutRoomsUtil.joinParams( user.name, userId + "-" + roomSequence, @@ -50,8 +50,7 @@ object BreakoutHdlrHelpers extends SystemConfiguration { externalMeetingId, user.avatar, user.webcamBackground, - user.role, - password + user.role ) // We generate a first url with redirect -> true redirectBaseString = BreakoutRoomsUtil.createBaseString(redirectParams) diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/apps/BreakoutRoomsUtilSpec.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/apps/BreakoutRoomsUtilSpec.scala index 0af4074b0833..bb00342ac87e 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/apps/BreakoutRoomsUtilSpec.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/apps/BreakoutRoomsUtilSpec.scala @@ -21,13 +21,13 @@ class BreakoutRoomsUtilSpec extends UnitSpec { } it should "create a base string" in { - val baseString = "fullName=User+4621018&isBreakout=true&meetingID=random-1853792&password=mp&redirect=true" + val baseString = "fullName=User+4621018&isBreakout=true&meetingID=random-1853792&redirect=true&role=MODERATOR" val params = new collection.mutable.HashMap[String, String] params += "fullName" -> BreakoutRoomsUtil.urlEncode("User 4621018") params += "isBreakout" -> BreakoutRoomsUtil.urlEncode("true") params += "meetingID" -> BreakoutRoomsUtil.urlEncode("random-1853792") - params += "password" -> BreakoutRoomsUtil.urlEncode("mp") + params += "role" -> BreakoutRoomsUtil.urlEncode("MODERATOR") params += "redirect" -> BreakoutRoomsUtil.urlEncode("true") val result = BreakoutRoomsUtil.createBaseString(params.toMap) @@ -36,18 +36,18 @@ class BreakoutRoomsUtilSpec extends UnitSpec { it should "calculate the checksum of join url" in { val sharedSecret = "a820d30da2db356124fce5bd5d8054b4" - val checksum = "6baef866df491ae82df992eb14f7f8511d5b77f3" - val baseString = "fullName=User+4621018&isBreakout=true&meetingID=random-1853792&password=mp&redirect=true" + val checksum = "3e84497a600e67476b73c4bb9cce3a3de413dcfa" + val baseString = "fullName=User+4621018&isBreakout=true&meetingID=random-1853792&redirect=true&role=MODERATOR" val joinChecksum = BreakoutRoomsUtil.calculateChecksum("join", baseString, sharedSecret) assert(joinChecksum == checksum) } it should "create a join api url" in { - val baseString = "fullName=User+4621018&isBreakout=true&meetingID=random-1853792&password=mp&redirect=true" + val baseString = "fullName=User+4621018&isBreakout=true&meetingID=random-1853792&redirect=true&role=MODERATOR" val webAPI = "http://www.example.com/bigbluebutton/api/" val joinAPI = "join" - val checksum = "6baef866df491ae82df992eb14f7f8511d5b77f3" + val checksum = "3e84497a600e67476b73c4bb9cce3a3de413dcfa" val joinURL = webAPI.concat(joinAPI).concat("?").concat(baseString).concat("&checksum=").concat(checksum) val result = BreakoutRoomsUtil.createJoinURL(webAPI, joinAPI, baseString, checksum); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java index 2bcb3cb621c0..db4f8571e234 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java @@ -59,7 +59,6 @@ public class ApiParams { public static final String ALLOW_MODS_TO_EJECT_CAMERAS = "allowModsToEjectCameras"; public static final String NAME = "name"; public static final String PARENT_MEETING_ID = "parentMeetingID"; - public static final String PASSWORD = "password"; public static final String RECORD = "record"; public static final String RECORD_ID = "recordID"; public static final String REDIRECT = "redirect"; diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/model/constraint/JoinPasswordConstraint.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/model/constraint/JoinPasswordConstraint.java deleted file mode 100755 index 181796f16d28..000000000000 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/model/constraint/JoinPasswordConstraint.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.bigbluebutton.api.model.constraint; - -import org.bigbluebutton.api.model.validator.JoinPasswordValidator; - -import jakarta.validation.Constraint; -import jakarta.validation.Payload; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Constraint(validatedBy = JoinPasswordValidator.class) -@Target(TYPE) -@Retention(RUNTIME) -public @interface JoinPasswordConstraint { - - String key() default "invalidPassword"; - String message() default "The provided password is neither a moderator or attendee password"; - Class[] groups() default {}; - Class[] payload() default {}; -} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/model/request/JoinMeeting.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/model/request/JoinMeeting.java index 4a942726f750..420595ffabf7 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/model/request/JoinMeeting.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/model/request/JoinMeeting.java @@ -2,10 +2,7 @@ import org.bigbluebutton.api.model.constraint.*; import org.bigbluebutton.api.model.shared.Checksum; -import org.bigbluebutton.api.model.shared.JoinPassword; -import org.bigbluebutton.api.model.shared.Password; -import jakarta.validation.Valid; import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; @@ -19,7 +16,6 @@ public enum Params implements RequestParameters { MEETING_ID("meetingID"), USER_ID("userID"), FULL_NAME("fullName"), - PASSWORD("password"), GUEST("guest"), AUTH("auth"), CREATE_TIME("createTime"), @@ -42,9 +38,6 @@ public enum Params implements RequestParameters { @NotEmpty(key = "missingParamFullName", message = "You must provide your name") private String fullName; - @PasswordConstraint - private String password; - @IsBooleanConstraint(message = "Guest must be a boolean value (true or false)") private String guestString; private Boolean guest; @@ -59,12 +52,8 @@ public enum Params implements RequestParameters { private String role; - @Valid - private Password joinPassword; - public JoinMeeting(Checksum checksum, HttpServletRequest servletRequest) { super(checksum, servletRequest); - joinPassword = new JoinPassword(); } @Override @@ -96,14 +85,6 @@ public void setFullName(String fullName) { this.fullName = fullName; } - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - public void setGuestString(String guestString) { this.guestString = guestString; } public Boolean getGuest() { @@ -146,18 +127,11 @@ public void setRole(String role) { public void populateFromParamsMap(Map params) { if(params.containsKey(Params.MEETING_ID.getValue())) { setMeetingID(params.get(Params.MEETING_ID.getValue())[0]); - joinPassword.setMeetingID(meetingID); } if(params.containsKey(Params.USER_ID.getValue())) setUserID(params.get(Params.USER_ID.getValue())[0]); if(params.containsKey(Params.FULL_NAME.getValue())) setFullName(params.get(Params.FULL_NAME.getValue())[0]); - if(params.containsKey(Params.PASSWORD.getValue())) { - setPassword(params.get(Params.PASSWORD.getValue())[0]); - joinPassword.setPassword(password); - } - - if(params.containsKey(Params.GUEST.getValue())) setGuestString(params.get(Params.GUEST.getValue())[0]); if(params.containsKey(Params.AUTH.getValue())) setAuthString(params.get(Params.AUTH.getValue())[0]); if(params.containsKey(Params.CREATE_TIME.getValue())) setCreateTimeString(params.get(Params.CREATE_TIME.getValue())[0]); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/model/shared/JoinPassword.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/model/shared/JoinPassword.java deleted file mode 100755 index 945a565e719f..000000000000 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/model/shared/JoinPassword.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.bigbluebutton.api.model.shared; - -import org.bigbluebutton.api.model.constraint.JoinPasswordConstraint; - -@JoinPasswordConstraint -public class JoinPassword extends Password {} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/model/validator/JoinPasswordValidator.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/model/validator/JoinPasswordValidator.java deleted file mode 100755 index 316a33c70c84..000000000000 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/model/validator/JoinPasswordValidator.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.bigbluebutton.api.model.validator; - -import org.bigbluebutton.api.domain.Meeting; -import org.bigbluebutton.api.model.constraint.JoinPasswordConstraint; -import org.bigbluebutton.api.model.shared.JoinPassword; -import org.bigbluebutton.api.service.ServiceUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; - -public class JoinPasswordValidator implements ConstraintValidator { - - private static Logger log = LoggerFactory.getLogger(JoinPasswordValidator.class); - - @Override - public void initialize(JoinPasswordConstraint constraintAnnotation) {} - - @Override - public boolean isValid(JoinPassword joinPassword, ConstraintValidatorContext constraintValidatorContext) { - if(joinPassword.getMeetingID() == null) { - return false; - } - - Meeting meeting = ServiceUtils.findMeetingFromMeetingID(joinPassword.getMeetingID()); - - if(meeting == null) { - return false; - } - - String moderatorPassword = meeting.getModeratorPassword(); - String attendeePassword = meeting.getViewerPassword(); - String providedPassword = joinPassword.getPassword(); - - return true; - } -} diff --git a/bigbluebutton-tests/playwright/core/helpers.ts b/bigbluebutton-tests/playwright/core/helpers.ts index 28fd691de468..56b46a9c869a 100644 --- a/bigbluebutton-tests/playwright/core/helpers.ts +++ b/bigbluebutton-tests/playwright/core/helpers.ts @@ -147,12 +147,12 @@ export async function createMeeting( export function getJoinURL({ meetingID, fullName, options }: GetJoinUrlProp): string { const { isModerator, joinParameter, skipSessionDetailsModal } = options || {}; - const pw = isModerator ? parameters.moderatorPW : parameters.attendeePW; + const role = isModerator ? 'MODERATOR' : 'VIEWER'; const shouldSkipSessionDetailsModal = skipSessionDetailsModal ? '&userdata-bbb_show_session_details_on_join=false' : ''; // default value in settings.yml is true const baseQuery = `fullName=${fullName}&meetingID=${meetingID}` - + `&password=${pw}${shouldSkipSessionDetailsModal}`; // prettier-ignore + + `&role=${role}${shouldSkipSessionDetailsModal}`; // prettier-ignore const query = joinParameter !== undefined ? `${baseQuery}&${joinParameter}` : baseQuery; const apiCall = `join${query}${parameters.secret}`; const checksum = getChecksum(apiCall, parameters.secret!); diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy index e7ae41c0e9ec..7cd408fd73ad 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -369,8 +369,6 @@ class ApiController { String fullName = ParamsUtil.stripControlChars(params.fullName) - String attPW = params.password - Meeting meeting = ServiceUtils.findMeetingFromMeetingID(params.meetingID); // the createTime mismatch with meeting's createTime, complain @@ -396,66 +394,25 @@ class ApiController { } // Now determine if this user is a moderator or a viewer. + // The user's role is set exclusively through the `role` parameter. + // The legacy password-based role selection (`password` parameter, matched + // against the meeting's moderator/attendee passwords) was deprecated in + // BigBlueButton 3.0 and removed in 4.0 — use `role` (MODERATOR or VIEWER) instead. String role = null; - // First Case: send a valid role if (!StringUtils.isEmpty(params.role) && roles.containsKey(params.role.toLowerCase())) { role = roles.get(params.role.toLowerCase()); - - // Second case: role is not present or valid BUT there is password - } else if (attPW != null && !attPW.isEmpty()){ - // Check if the meeting has passwords - if ((meeting.getModeratorPassword() != null && !meeting.getModeratorPassword().isEmpty()) - && (meeting.getViewerPassword() != null && !meeting.getViewerPassword().isEmpty())){ - // Check which role does the user belong - if (meeting.getModeratorPassword().equals(attPW)) { - role = Meeting.ROLE_MODERATOR - } else if (meeting.getViewerPassword().equals(attPW)) { - role = Meeting.ROLE_ATTENDEE - } else { - log.debug("Password does not match any of the registered ones"); - response.addHeader("Cache-Control", "no-cache") - withFormat { - xml { - render(text: responseBuilder.buildError("Params required", "You must enter a valid password", - RESP_CODE_FAILED), contentType: "text/xml") - } - '*' { - render(text: responseBuilder.buildError("Params required", "You must enter a valid password", - RESP_CODE_FAILED), contentType: "text/xml") - } - } - return - } - // In this case, the meeting doesn't have any password registered and there is no role param - } else { - log.debug("This meeting doesn't have any password"); - response.addHeader("Cache-Control", "no-cache") - withFormat { - xml { - render(text: responseBuilder.buildError("Params required", "You must send the 'role' parameter, since " + - "this meeting doesn't have any password.", RESP_CODE_FAILED), contentType: "text/xml") - } - '*' { - render(text: responseBuilder.buildError("Params required", "You must send the 'role' parameter, since " + - "this meeting doesn't have any password.", RESP_CODE_FAILED), contentType: "text/xml") - } - } - return - } - - // Third case: No valid role + no valid password } else { - log.debug("No matching params encountered"); + log.debug("No valid role provided"); response.addHeader("Cache-Control", "no-cache") withFormat { xml { - render(text: responseBuilder.buildError("Params required", "You must either send the valid role of the user, or " + - "the password, sould the meeting has one.", RESP_CODE_FAILED), contentType: "text/xml") + render(text: responseBuilder.buildError("Params required", "You must provide a valid 'role' parameter " + + "(MODERATOR or VIEWER).", RESP_CODE_FAILED), contentType: "text/xml") } '*' { - render(text: responseBuilder.buildError("Params required", "You must either send the valid role of the user, or " + - "the password, sould the meeting has one.", RESP_CODE_FAILED), contentType: "text/xml") + render(text: responseBuilder.buildError("Params required", "You must provide a valid 'role' parameter " + + "(MODERATOR or VIEWER).", RESP_CODE_FAILED), contentType: "text/xml") } } return diff --git a/bigbluebutton-web/src/test/groovy/org/bigbluebutton/web/controllers/ApiControllerSpec.groovy b/bigbluebutton-web/src/test/groovy/org/bigbluebutton/web/controllers/ApiControllerSpec.groovy index 7a82b245e4b6..35d507bc2cfd 100644 --- a/bigbluebutton-web/src/test/groovy/org/bigbluebutton/web/controllers/ApiControllerSpec.groovy +++ b/bigbluebutton-web/src/test/groovy/org/bigbluebutton/web/controllers/ApiControllerSpec.groovy @@ -178,10 +178,9 @@ class ApiControllerSpec extends Specification implements ControllerUnitTestThe meeting ID that identifies the meeting you are attempting to join.) }, { - "name": "password", + "name": "role", "required": true, "type": "String", - deprecated: true, "description": ( <> -

This password value is used to determine the role of the user. It must match either the moderator or attendee password.

-

Deprecated: Use the role parameter instead to directly set the user's role. If the role parameter is passed, then the password parameter is not required.

+

Defines the user's role for the meeting. Valid values are MODERATOR or VIEWER (case insensitive).

+

Note: the legacy password parameter — used to derive the role by matching the meeting's moderator/attendee password — was deprecated in BigBlueButton 3.0 and removed in 4.0. Use role instead.

) }, - { - "name": "role", - "required": true, - "type": "String", - "description": (<>Define user role for the meeting. Valid values are MODERATOR or VIEWER (case insensitive). If the role parameter is present and valid, it overrides the password parameter. You must specify either password parameter or role parameter in the join request.) - }, { "name": "createTime", "required": false, diff --git a/docs/docs/development/api.md b/docs/docs/development/api.md index 224b4ee855a9..59b21b38156f 100644 --- a/docs/docs/development/api.md +++ b/docs/docs/development/api.md @@ -136,6 +136,7 @@ Updated in 4.0: - **Changed:** Parameter `meetingLayout` default is now `UNIFIED_LAYOUT`. **Removed:** `meetingLayout` no longer supports `CUSTOM_LAYOUT`, `SMART_LAYOUT`, `PRESENTATION_FOCUS`, `VIDEO_FOCUS`. The remaining non-default options targeting hybrid/niche scenarios are `CAMERAS_ONLY`, `PARTICIPANTS_AND_CHAT_ONLY`, `PRESENTATION_ONLY`, `MEDIA_ONLY`. - **Removed option:** `layouts` is no longer a valid `disabledFeatures` value (the layout selection UI was removed). - **join** + - **Removed:** the `password` parameter. Password-based role selection (matching the meeting's `moderatorPW`/`attendeePW`) was deprecated in 3.0 and is no longer accepted — use the `role` parameter (`MODERATOR` or `VIEWER`) instead, which has been available since 2.4. - **Changed:** Parameter `enforceLayout` accepted values are now `UNIFIED_LAYOUT`, `CAMERAS_ONLY`, `PARTICIPANTS_AND_CHAT_ONLY`, `PRESENTATION_ONLY`, `MEDIA_ONLY` (the deprecated `CUSTOM_LAYOUT`, `SMART_LAYOUT`, `PRESENTATION_FOCUS`, `VIDEO_FOCUS` are no longer accepted). - **clientSettings** - The deprecated REST endpoint `/api/rest/clientSettings` was **removed**. Client settings are now served through the GraphQL stack.