Skip to content

Commit

Permalink
Restrict access to endpoints for somre roles (#1000)
Browse files Browse the repository at this point in the history
- USERs can only view and not edit
- TRANSLATORs can translate
- ADMINs, PMs can use more management functions
  • Loading branch information
mensinda authored Mar 1, 2024
1 parent 2c80f5a commit 5583821
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
Expand Down Expand Up @@ -175,6 +176,22 @@ protected void configure(HttpSecurity http) throws Exception {
. // user management is only allowed for ADMINs and PMs
antMatchers("/api/users/**")
.hasAnyRole("PM", "ADMIN")
. // Read-only access is OK for users
antMatchers(HttpMethod.GET, "/api/textunits/**")
.authenticated()
. // Searching is also OK for users
antMatchers(HttpMethod.POST, "/api/textunits/search")
.authenticated()
. // USERs are not allowed to change translations
antMatchers("/api/textunits/**")
.hasAnyRole("TRANSLATOR", "PM", "ADMIN")
. // Read-only is OK for everyone
antMatchers(HttpMethod.GET, "/api/**")
.authenticated()
. // However, all other methods require is PM and ADMIN only unless overwritten
// above
antMatchers("/api/**")
.hasAnyRole("PM", "ADMIN")
. // local access only for rotation management and logger config
antMatchers("/**")
.authenticated() // everything else must be authenticated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Paginator from "../widgets/Paginator";
import RepositoryStore from "../../stores/RepositoryStore";
import SearchParamsStore from "../../stores/workbench/SearchParamsStore";
import SearchConstants from "../../utils/SearchConstants";
import AuthorityService from "../../utils/AuthorityService";


class BranchesPage extends React.Component {
Expand Down Expand Up @@ -233,6 +234,7 @@ class BranchesPage extends React.Component {
onDelete={() => {
BranchesScreenshotViewerActions.delete();
}}
disableDelete={!AuthorityService.canEditScreenshots()}
/>
</AltContainer>

Expand Down
5 changes: 3 additions & 2 deletions webapp/src/main/resources/public/js/components/drops/Drops.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import ImportDropConfig from "../../sdk/drop/ImportDropConfig";
import NewDropModal from "./NewDropModal";
import RepositoryActions from "../../actions/RepositoryActions";
import ConfirmationModal from "../widgets/ConfirmationModal";
import AuthorityService from "../../utils/AuthorityService";

let Drops = createReactClass({
displayName: 'Drops',
Expand Down Expand Up @@ -276,7 +277,7 @@ let Drops = createReactClass({

return (
<tr key={`Drops.repositoryRow.${drop.name}.${drop.repository.name}`} className={rowClass}>
<td>{drop.name}{this.getButtonControlBar(drop)}</td>
<td>{drop.name}{AuthorityService.canEditProjectRequests() && this.getButtonControlBar(drop)}</td>
<td>{drop.repository.name}</td>
<td><FormattedNumber value={wordCount}/></td>
<td><FormattedDate value={drop.createdDate} day="numeric" month="long" year="numeric"/></td>
Expand Down Expand Up @@ -463,7 +464,7 @@ let Drops = createReactClass({
return (
<div className="pull-right mrm mlm">
<Button bsStyle="primary" bsSize="small" className="new-request-button"
onClick={this.onClickNewRequest}>
onClick={this.onClickNewRequest} disabled={!AuthorityService.canEditProjectRequests()}>
<FormattedMessage id="drops.newRequest.btn"/>
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {StatusCommonTypes} from "./StatusCommon";


class Screenshot extends React.Component {

static propTypes = {
"screenshot": PropTypes.object.isRequired,
"onClick": PropTypes.func.isRequired,
Expand All @@ -18,6 +18,7 @@ class Screenshot extends React.Component {
"onNameClick": PropTypes.func.isRequired,
"onStatusChanged": PropTypes.func.isRequired,
"onStatusGlyphClick": PropTypes.func.isRequired,
"statusGlyphDisabled": PropTypes.bool.isRequired,
}

componentDidMount() {
Expand Down Expand Up @@ -66,14 +67,14 @@ class Screenshot extends React.Component {
render() {

let screenshotClassName = "screenshot";

if (this.props.isSelected) {
screenshotClassName += " screenshot-selected";
}

return (
<div
ref="screenshot"
<div
ref="screenshot"
className={screenshotClassName}
onClick={this.props.onClick}
tabIndex={0}
Expand All @@ -83,20 +84,21 @@ class Screenshot extends React.Component {
<img src={this.props.screenshot.src} />
</div>
<div className="screenshot-description">
<Label bsStyle='primary'
bsSize='large'
<Label bsStyle='primary'
bsSize='large'
className="mrxs mtl clickable"
onClick={(e) => this.onLocaleClick(e)}>
{this.props.screenshot.locale.bcp47Tag}
</Label>
</Label>

<span onClick={(e) => this.onNameClick(e)} className="clickable">
{this.props.screenshot.name}
</span>
</div>
<span className="screenshot-glyph">
<StatusGlyph status={this.props.screenshot.status}
<StatusGlyph status={this.props.screenshot.status}
onClick={this.props.onStatusGlyphClick}
disabled={this.props.statusGlyphDisabled}
/>
</span >
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class ScreenshotViewerModal extends React.Component {
"onGoToPrevious": PropTypes.func.isRequired,
"onGoToNext": PropTypes.func.isRequired,
"onDelete": PropTypes.func.isRequired,
"error": PropTypes.bool
"error": PropTypes.bool,
"disableDelete": PropTypes.bool.isRequired,
}

popover() {
Expand Down Expand Up @@ -96,7 +97,7 @@ class ScreenshotViewerModal extends React.Component {
<div className="branches-screenshotviewer-modal-delete-container">
{this.props.isDeleting ? <span className="glyphicon glyphicon-refresh spinning"/> :
<OverlayTrigger trigger="click" placement="top" overlay={this.popover()}>
<Button bsStyle="danger" style={{fontSize: 11}}>
<Button bsStyle="danger" style={{fontSize: 11}} disabled={this.props.disableDelete}>
<FormattedMessage id="label.delete"/>
</Button>
</OverlayTrigger>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Screenshot from "./Screenshot";
import ScreenshotsTextUnit from "./ScreenshotsTextUnit";

class ScreenshotsGrid extends React.Component {

static propTypes = {
"screenshotsData": PropTypes.array.isRequired,
"selectedScreenshotIdx": PropTypes.number,
Expand All @@ -18,9 +18,10 @@ class ScreenshotsGrid extends React.Component {
"onLocaleClick": PropTypes.func.isRequired,
"onNameClick": PropTypes.func.isRequired,
"onStatusGlyphClick": PropTypes.func.isRequired,
"onStatusChanged": PropTypes.func.isRequired
"onStatusChanged": PropTypes.func.isRequired,
"statusGlyphDisabled": PropTypes.bool.isRequired,
}

getSelectedScreenshot() {
let selectedScreenshot = null;

Expand Down Expand Up @@ -51,9 +52,9 @@ class ScreenshotsGrid extends React.Component {

let locale = this.getSelectedScreenshot().locale.bcp47Tag;

return <ScreenshotsTextUnit
return <ScreenshotsTextUnit
key={textUnit.id}
textUnit={textUnit}
textUnit={textUnit}
onNameClick={(e) => this.props.onScreenshotsTextUnitNameClick(e, textUnit, locale)}
onTargetClick={(e) => this.props.onScreenshotsTextUnitTargetClick(e, textUnit, locale)}
/>
Expand All @@ -66,13 +67,14 @@ class ScreenshotsGrid extends React.Component {
return this.props.screenshotsData.map((screenshot, idx) =>
<Screenshot
key={screenshot.name + '_' + idx}
screenshot={screenshot}
isSelected={idx === this.props.selectedScreenshotIdx}
screenshot={screenshot}
isSelected={idx === this.props.selectedScreenshotIdx}
onClick={() => this.props.onScreenshotClicked(idx)}
onLocaleClick={ () => this.props.onLocaleClick([screenshot.locale.bcp47Tag])}
onNameClick={() => this.props.onNameClick(screenshot.name)}
onStatusGlyphClick={() => this.props.onStatusGlyphClick(idx)}
onStatusChanged={(status) => this.props.onStatusChanged({status: status, idx: idx})}
statusGlyphDisabled={this.props.statusGlyphDisabled}
/>)
}

Expand All @@ -92,16 +94,16 @@ class ScreenshotsGrid extends React.Component {

return (
<div>
<ReactSidebarResponsive
ref="sideBarScreenshot"
<ReactSidebarResponsive
ref="sideBarScreenshot"
sidebar={this.renderSideBar()}
rootClassName="side-bar-root-container-screenshot"
sidebarClassName="side-bar-container-screenshot"
contentClassName="side-bar-main-content-container-screenshot"
docked={true} pullRight={true} transitions={false}>

{this.renderScreenshots()}

</ReactSidebarResponsive>
</div>);
}
Expand All @@ -111,15 +113,15 @@ class ScreenshotsGrid extends React.Component {
*/
render() {
var res;

if (this.props.searching) {
res = null;
} else if (this.props.screenshotsData.length > 0) {
res = this.renderWithResults();
} else {
res =this.renderNoResults();
}

return res;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import StatusDropdown from "./StatusDropdown";
import ScreenshotReviewModal from "./ScreenshotReviewModal";
import ScreenshotsRepositoryActions from "../../actions/screenshots/ScreenshotsRepositoryActions";
import ScreenshotsLocaleStore from "../../stores/screenshots/ScreenshotsLocaleStore";
import AuthorityService from "../../utils/AuthorityService";

class ScreenshotsPage extends React.Component {

Expand Down Expand Up @@ -281,9 +282,13 @@ class ScreenshotsPage extends React.Component {
ScreenshotsPageActions.performSearch();
}}
onStatusGlyphClick={(screenshotIdx) => {
if (!AuthorityService.canEditScreenshots()) {
return;
}
ScreenshotsReviewModalActions.openWithScreenshot(screenshotIdx);
}}
onStatusChanged={ScreenshotActions.changeStatus}
statusGlyphDisabled={!AuthorityService.canEditScreenshots()}
/>
</AltContainer>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ class StatusGlyph extends React.Component {
*/
render() {
return (
<Glyphicon
glyph={this.getGlyph(this.props.status)}
title={StatusCommon.getScreenshotStatusIntl(this.props.intl, this.props.status)}
<Glyphicon
glyph={this.getGlyph(this.props.status)}
title={StatusCommon.getScreenshotStatusIntl(this.props.intl, this.props.status)}
onClick={this.props.onClick}
disabled={this.props.disabled}
className="btn"
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import AuthorityService from "../../utils/AuthorityService";

class UserManagement extends React.Component {
render() {
if (!AuthorityService.hasPermissionsForUserManagement()) {
if (!AuthorityService.canViewUserManagement()) {
return (
<div className="ptl">
<h3 className="text-center mtl"><FormattedMessage id="users.forbidden"/></h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import AltContainer from "alt-container";
import ViewModeStore from "../../stores/workbench/ViewModeStore";
import ViewModeDropdown from "./ViewModeDropdown";
import ViewModeActions from "../../actions/workbench/ViewModeActions";
import AuthorityService from "../../utils/AuthorityService";

let SearchResults = createReactClass({
displayName: 'SearchResults',
Expand Down Expand Up @@ -559,7 +560,8 @@ let SearchResults = createReactClass({
let numberOfSelectedTextUnits = selectedTextUnits.length;
let isAtLeastOneTextUnitSelected = numberOfSelectedTextUnits >= 1;

let actionButtonsDisabled = isSearching || !isAtLeastOneTextUnitSelected;
let selectorCheckBoxDisabled = !AuthorityService.canEditTranslations();
let actionButtonsDisabled = isSearching || !isAtLeastOneTextUnitSelected || !AuthorityService.canEditTranslations();
let nextPageButtonDisabled = isSearching || noMoreResults;
let previousPageButtonDisabled = isSearching || isFirstPage;

Expand Down Expand Up @@ -595,7 +597,7 @@ let SearchResults = createReactClass({
<ViewModeDropdown onModeSelected={(mode) => ViewModeActions.changeViewMode(mode)}/>
</AltContainer>

<TextUnitSelectorCheckBox numberOfSelectedTextUnits={numberOfSelectedTextUnits}/>
<TextUnitSelectorCheckBox numberOfSelectedTextUnits={numberOfSelectedTextUnits} disabled={selectorCheckBoxDisabled}/>
<Button bsSize="small" disabled={previousPageButtonDisabled}
onClick={this.onFetchPreviousPageClicked}><span
className="glyphicon glyphicon-chevron-left"></span></Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import WorkbenchActions from "../../actions/workbench/WorkbenchActions";
import GitBlameActions from "../../actions/workbench/GitBlameActions";
import TranslationHistoryActions from "../../actions/workbench/TranslationHistoryActions";
import Locales from "../../utils/Locales";
import AuthorityService from "../../utils/AuthorityService";
import {
Grid,
Row,
Expand Down Expand Up @@ -450,7 +451,7 @@ let TextUnit = createReactClass({
}
ui = (
<Glyphicon glyph={glyphType} id="reviewStringButton" title={glyphTitle} className="btn"
onClick={this.onTextUnitGlyphClicked}/>
onClick={this.onTextUnitGlyphClicked} disabled={!AuthorityService.canEditTranslations()}/>
);
}

Expand Down Expand Up @@ -520,6 +521,9 @@ let TextUnit = createReactClass({
editStringClicked(e) {
e.stopPropagation();

if (!AuthorityService.canEditTranslations()) {
return;
}
this.setState({
isEditMode: true
}, () => {
Expand All @@ -535,7 +539,7 @@ let TextUnit = createReactClass({
*/
getTargetStringUI() {
let ui;
if (this.state.isEditMode) {
if (this.state.isEditMode && AuthorityService.canEditTranslations()) {
ui = this.getUIForEditMode();
} else {
let targetString = this.hasTargetChanged() ? this.state.translation : this.props.translation;
Expand All @@ -544,7 +548,10 @@ let TextUnit = createReactClass({
let trailingWhitespacesSymbol = "";

let noTranslation = false;
let targetClassName = "pts pls pbs textunit-string textunit-target";
let targetClassName = "pts pls pbs textunit-string";
if (AuthorityService.canEditTranslations()) {
targetClassName += " textunit-target"
}
if (targetString == null) {
noTranslation = true;
dir = Locales.getLanguageDirection(Locales.getCurrentLocale());
Expand Down Expand Up @@ -699,6 +706,10 @@ let TextUnit = createReactClass({
* @param {SyntheticEvent} e
*/
onTextUnitClick(e) {
if (!AuthorityService.canEditTranslations()) {
return;
}

// NOTE: if text has been selected for this textunit, don't activate it because the user's intention is to
// select text, not activate textunit.
if (!window.getSelection().toString()) {
Expand All @@ -711,7 +722,7 @@ let TextUnit = createReactClass({
*/
getTextUnitReviewModal() {
let ui = "";
if (this.state.isShowModal) {
if (this.state.isShowModal && AuthorityService.canEditTranslations()) {
let textUnitArray = [this.getCloneOfTextUnitFromProps()];
ui = (
<TextUnitsReviewModal isShowModal={this.state.isShowModal}
Expand Down Expand Up @@ -1002,7 +1013,7 @@ let TextUnit = createReactClass({
<div className="text-unit-root">
<div className="left mls">
<span style={{gridArea: "cb"}} className="mrxs">
<input type="checkbox" checked={isSelected} readOnly={true}/>
<input type="checkbox" checked={isSelected} readOnly={true} disabled={!AuthorityService.canEditTranslations()}/>
</span>
<div style={{gridArea: "locale"}}>
<Label bsStyle='primary' bsSize='large' className="clickable" onClick={this.onLocaleLabelClick}>
Expand Down
Loading

0 comments on commit 5583821

Please sign in to comment.