From 4558dce9f909ad8bcb49322229a5b8de07562dcc Mon Sep 17 00:00:00 2001 From: SammyIsConfused Date: Fri, 25 Jan 2019 16:28:31 +1100 Subject: [PATCH] Login HTML Notice: create notice (#720) * Add route and menu option for login notice editor * Add Rest API and Service * Create initial prototype of login notice editor page * Add IDs to the textfield and button, remove page reload * Remove Iterable from single permission check * Add apereo headers * Make changes suggested in edalex-ian's code review -Remove unnecessary braces in LoginNoticeConfigPage.tsx -Reword login notice settings description -Remove warning suppression -Minor formatting changes to LoginNoticeServiceImpl.java * Refactor styling into SettingsMenuContainer * Remove unnecessary braces --- .../com.equella.core/js/src/MainUI/Main.purs | 3 +- .../js/src/MainUI/Routes.purs | 7 +- .../com.equella.core/js/src/TSComponents.js | 1 + .../com.equella.core/js/src/TSComponents.purs | 2 + .../tsrc/components/SettingsMenuContainer.tsx | 27 +++++++ .../loginnotice/LoginNoticeConfigPage.tsx | 80 +++++++++++++++++++ .../lang/i18n-resource-centre.properties | 2 + .../com/tle/web/settings/SettingsList.scala | 8 +- .../loginnotice/LoginNoticeService.java | 27 +++++++ .../impl/LoginNoticeServiceImpl.java | 71 ++++++++++++++++ .../api/loginnotice/LoginNoticeResource.java | 44 ++++++++++ .../impl/LoginNoticeResourceImpl.java | 54 +++++++++++++ .../remoting/resteasy/RestEasyServlet.java | 4 +- 13 files changed, 323 insertions(+), 7 deletions(-) create mode 100644 Source/Plugins/Core/com.equella.core/js/tsrc/components/SettingsMenuContainer.tsx create mode 100644 Source/Plugins/Core/com.equella.core/js/tsrc/loginnotice/LoginNoticeConfigPage.tsx create mode 100644 Source/Plugins/Core/com.equella.core/src/com/tle/core/settings/loginnotice/LoginNoticeService.java create mode 100644 Source/Plugins/Core/com.equella.core/src/com/tle/core/settings/loginnotice/impl/LoginNoticeServiceImpl.java create mode 100644 Source/Plugins/Core/com.equella.core/src/com/tle/web/api/loginnotice/LoginNoticeResource.java create mode 100644 Source/Plugins/Core/com.equella.core/src/com/tle/web/api/loginnotice/impl/LoginNoticeResourceImpl.java diff --git a/Source/Plugins/Core/com.equella.core/js/src/MainUI/Main.purs b/Source/Plugins/Core/com.equella.core/js/src/MainUI/Main.purs index f9bb5c921f..a4dec11244 100644 --- a/Source/Plugins/Core/com.equella.core/js/src/MainUI/Main.purs +++ b/Source/Plugins/Core/com.equella.core/js/src/MainUI/Main.purs @@ -23,7 +23,7 @@ import OEQ.Utils.Polyfills (polyfill) import React (component, unsafeCreateLeafElement) import React.DOM (div') import Routing.PushState (matchesWith) -import TSComponents (courseEdit, coursesPage, themePageClass) +import TSComponents (courseEdit, coursesPage, themePageClass, loginNoticeConfigPageClass) import Web.HTML (window) import Web.HTML.Location (pathname) import Web.HTML.Window (location) @@ -60,6 +60,7 @@ main = do CoursesPage -> coursesPage NewCourse -> courseEdit Nothing ThemePage -> unsafeCreateLeafElement themePageClass {bridge:tsBridge} + LoginNoticeConfigPage -> unsafeCreateLeafElement loginNoticeConfigPageClass {bridge:tsBridge} CourseEdit cid -> courseEdit $ Just cid ViewItemPage (ItemRef uuid version) -> viewItemPage {uuid,version} LegacyPage page -> legacy {page} diff --git a/Source/Plugins/Core/com.equella.core/js/src/MainUI/Routes.purs b/Source/Plugins/Core/com.equella.core/js/src/MainUI/Routes.purs index 336123c92b..270a6b8d82 100644 --- a/Source/Plugins/Core/com.equella.core/js/src/MainUI/Routes.purs +++ b/Source/Plugins/Core/com.equella.core/js/src/MainUI/Routes.purs @@ -44,7 +44,8 @@ data Route = CourseEdit String | ViewItemPage ItemRef | ThemePage | - NewCourse + NewCourse | + LoginNoticeConfigPage navGlobals :: forall route. {nav::PushStateInterface, preventNav :: Ref (EffectFn1 route Boolean)} navGlobals = unsafePerformEffect do @@ -80,7 +81,8 @@ routeMatch = NewCourse <$ (lit "course" *> lit "new") <|> CourseEdit <$> (lit "course" *> str <* lit "edit") <|> CoursesPage <$ (lit "course") <|> - ThemePage <$ (lit "themeconfiguration")) + ThemePage <$ (lit "themeconfiguration") <|> + LoginNoticeConfigPage <$ (lit "loginconfiguration")) <|> (LegacyPage <$> legacyRoute) @@ -123,6 +125,7 @@ routeURI r = (case r of CoursesPage -> "page/course" NewCourse -> "page/course/new" ThemePage -> "page/themeconfiguration" + LoginNoticeConfigPage -> "page/loginconfiguration" CourseEdit cid -> "page/course/" <> cid <> "/edit" ViewItemPage (ItemRef uuid version) -> "integ/gen/" <> uuid <> "/" <> show version LegacyPage (LegacyURI path params) -> diff --git a/Source/Plugins/Core/com.equella.core/js/src/TSComponents.js b/Source/Plugins/Core/com.equella.core/js/src/TSComponents.js index ac7a3d8eae..343bb37a84 100644 --- a/Source/Plugins/Core/com.equella.core/js/src/TSComponents.js +++ b/Source/Plugins/Core/com.equella.core/js/src/TSComponents.js @@ -4,3 +4,4 @@ exports.editCourse = require("course/EditCourse").default; exports.appBarQueryClass = require("components/AppBarQuery").default; exports.courseSelectClass = require("components/CourseSelect").default; exports.themePageClass = require("theme/ThemePage").default; +exports.loginNoticeConfigPageClass = require("loginnotice/LoginNoticeConfigPage").default; diff --git a/Source/Plugins/Core/com.equella.core/js/src/TSComponents.purs b/Source/Plugins/Core/com.equella.core/js/src/TSComponents.purs index c1765fe732..9fed11990c 100644 --- a/Source/Plugins/Core/com.equella.core/js/src/TSComponents.purs +++ b/Source/Plugins/Core/com.equella.core/js/src/TSComponents.purs @@ -22,6 +22,8 @@ foreign import courseSelectClass :: forall a. ReactClass a foreign import themePageClass :: forall a. ReactClass a +foreign import loginNoticeConfigPageClass :: forall a. ReactClass a + coursesPage :: ReactElement coursesPage = unsafeCreateLeafElement searchCourses {store:store, bridge: tsBridge} diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/components/SettingsMenuContainer.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/components/SettingsMenuContainer.tsx new file mode 100644 index 0000000000..1bd6c86df6 --- /dev/null +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/components/SettingsMenuContainer.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; +import {createStyles, Paper, withStyles, WithStyles} from "@material-ui/core"; + +const styles = createStyles({ + container: { + margin: "8px", + padding: "8px" + } +}); + +class SettingsMenuContainer extends React.Component> { + + constructor(props: WithStyles) { + super(props); + }; + + render() { + const styles = this.props.classes; + return ( + + {this.props.children} + + ); + } +} + +export default withStyles(styles)(SettingsMenuContainer); diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/loginnotice/LoginNoticeConfigPage.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/loginnotice/LoginNoticeConfigPage.tsx new file mode 100644 index 0000000000..2e5e66b14f --- /dev/null +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/loginnotice/LoginNoticeConfigPage.tsx @@ -0,0 +1,80 @@ +import * as React from "react"; +import {Bridge} from "../api/bridge"; +import {prepLangStrings} from "../util/langstrings"; +import {Button, Grid, TextField} from "@material-ui/core"; +import axios, {AxiosResponse} from "axios"; +import {Config} from "../config"; +import SettingsMenuContainer from "../components/SettingsMenuContainer"; + +interface LoginNoticeConfigPageProps { + bridge: Bridge; +} + +interface LoginNoticeConfigPageState { + notice?: string +} + +export const strings = prepLangStrings("loginnoticepage", + { + title: "Login Notice Editor", + label: "Login Notice" + } +); + +class LoginNoticeConfigPage extends React.Component { + + constructor(props:LoginNoticeConfigPageProps) { + super(props); + }; + + state: LoginNoticeConfigPageState = { + notice: "" + }; + + handleSubmitNotice = () => { + axios.put(`${Config.baseUrl}api/loginnotice/settings/`, this.state.notice); + }; + + handleTextFieldChange = (e: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement) => { + this.setState({notice: e.value}); + }; + + componentDidMount = () => { + axios + .get(`${Config.baseUrl}api/loginnotice/settings/`) + .then((response: AxiosResponse) => + { + this.setState({notice: response.data}); + }); + }; + + render() { + const {Template} = this.props.bridge; + return ( + + ); + } +} +export default LoginNoticeConfigPage; diff --git a/Source/Plugins/Core/com.equella.core/resources/lang/i18n-resource-centre.properties b/Source/Plugins/Core/com.equella.core/resources/lang/i18n-resource-centre.properties index 8968272ad4..3c085bae4f 100644 --- a/Source/Plugins/Core/com.equella.core/resources/lang/i18n-resource-centre.properties +++ b/Source/Plugins/Core/com.equella.core/resources/lang/i18n-resource-centre.properties @@ -2521,6 +2521,8 @@ log.uploading=Uploading resource {0} loggedin=Logged in login.asuser=Login as user\: login.description=Configure login settings and authentication by IP address. Author a Login notice to display to users on login +loginnotice.settings.title=Login Notice Settings +loginnotice.settings.description=Configuration of notices during the login process. login.enable=Enable SSL login.enableanonacl=Enable IP address and HTTP referrer ACLs for anonymous requests login.enableloginip=Enable login via IP address diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/settings/SettingsList.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/settings/SettingsList.scala index d19bfda3a0..66a5d9104b 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/settings/SettingsList.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/settings/SettingsList.scala @@ -19,7 +19,6 @@ package com.tle.web.settings import com.tle.common.connectors.ConnectorConstants.{PRIV_CREATE_CONNECTOR, PRIV_EDIT_CONNECTOR} import com.tle.common.externaltools.constants.ExternalToolConstants import com.tle.common.lti.consumers.LtiConsumerConstants -import com.tle.common.security.SecurityConstants import com.tle.common.userscripts.UserScriptsConstants import com.tle.core.activation.service.CourseInfoService import com.tle.core.db.{DB, RunWithDB} @@ -60,7 +59,10 @@ object SettingsList { } val uiSettings = CoreSettingsRest("ui", "ui", "uisettings.name", "uisettings.desc", "api/settings/ui", - AclChecks.filterNonGrantedPrivileges(Iterable("EDIT_SYSTEM_SETTINGS"), false)) + AclChecks.filterNonGrantedPrivileges(Iterable("EDIT_SYSTEM_SETTINGS"),true)) + + val loginNoticeSettings = CoreSettingsPage("loginnotice", General, "loginnotice.settings.title", "loginnotice.settings.description","page/loginconfiguration", + () => !aclManager.filterNonGrantedPrivileges("EDIT_SYSTEM_SETTINGS").isEmpty) val echoSettings = CoreSettingsPage("echo", Integration, "echo.settings.title", "echo.settings.description", "access/echoservers.do", () => !aclManager.filterNonGrantedPrivileges(EchoConstants.PRIV_CREATE_ECHO, EchoConstants.PRIV_EDIT_ECHO).isEmpty) @@ -88,7 +90,7 @@ object SettingsList { val allSettings : mutable.Buffer[EditableSettings] = mutable.Buffer( connectorSettings, echoSettings, ltiConsumersSettings, userScriptSettings, - oauthSettings, htmlEditorSettings, externalToolsSettings, uiSettings, + oauthSettings, htmlEditorSettings, externalToolsSettings, uiSettings, loginNoticeSettings, CoreSettingsPage("shortcuts", General, "shortcuts.settings.title", "shortcuts.settings.description", "access/shortcuturlssettings.do", shortcutPrivProvider.isAuthorised), diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/settings/loginnotice/LoginNoticeService.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/settings/loginnotice/LoginNoticeService.java new file mode 100644 index 0000000000..722aa181d1 --- /dev/null +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/settings/loginnotice/LoginNoticeService.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Apereo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tle.core.settings.loginnotice; + +public interface LoginNoticeService +{ + String getNotice(); + + void setNotice(String notice); + + void deleteNotice(); + +} diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/settings/loginnotice/impl/LoginNoticeServiceImpl.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/settings/loginnotice/impl/LoginNoticeServiceImpl.java new file mode 100644 index 0000000000..edfe177163 --- /dev/null +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/settings/loginnotice/impl/LoginNoticeServiceImpl.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Apereo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tle.core.settings.loginnotice.impl; + +import com.tle.core.guice.Bind; +import com.tle.core.security.TLEAclManager; +import com.tle.core.settings.loginnotice.LoginNoticeService; +import com.tle.core.settings.service.ConfigurationService; +import com.tle.exceptions.PrivilegeRequiredException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collections; + +@Singleton +@Bind(LoginNoticeService.class) +public class LoginNoticeServiceImpl implements LoginNoticeService +{ + @Inject + TLEAclManager tleAclManager; + @Inject + ConfigurationService configurationService; + + private static final String PERMISSION_KEY = "EDIT_SYSTEM_SETTINGS"; + private static final String LOGIN_NOTICE_KEY = "login.notice.settings"; + + @Override + public String getNotice() + { + String loginNotice = configurationService.getProperty(LOGIN_NOTICE_KEY); + if(loginNotice != null) + { + return loginNotice; + } + return ""; + } + + @Override + public void setNotice(String notice) + { + checkPermissions(); + configurationService.setProperty(LOGIN_NOTICE_KEY, notice); + } + + @Override + public void deleteNotice() + { + checkPermissions(); + configurationService.deleteProperty(LOGIN_NOTICE_KEY); + } + + private void checkPermissions() { + if (tleAclManager.filterNonGrantedPrivileges(Collections.singleton(PERMISSION_KEY), false).isEmpty()) { + throw new PrivilegeRequiredException(PERMISSION_KEY); + } + } +} diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/web/api/loginnotice/LoginNoticeResource.java b/Source/Plugins/Core/com.equella.core/src/com/tle/web/api/loginnotice/LoginNoticeResource.java new file mode 100644 index 0000000000..123e1d8054 --- /dev/null +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/web/api/loginnotice/LoginNoticeResource.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Apereo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tle.web.api.loginnotice; + +import io.swagger.annotations.Api; + +import javax.ws.rs.*; +import javax.ws.rs.core.Response; + +/** + * @author Samantha Fisher + */ + +@Path("loginnotice/") +@Api("Login Notice") +public interface LoginNoticeResource +{ + @GET + @Produces("text/plain") + @Path("settings") + Response retrieveNotice(); + + @PUT + @Path("settings") + Response setNotice(String loginNotice); + + @DELETE + @Path("settings") + Response deleteNotice(); +} diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/web/api/loginnotice/impl/LoginNoticeResourceImpl.java b/Source/Plugins/Core/com.equella.core/src/com/tle/web/api/loginnotice/impl/LoginNoticeResourceImpl.java new file mode 100644 index 0000000000..f1200bd78e --- /dev/null +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/web/api/loginnotice/impl/LoginNoticeResourceImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Apereo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tle.web.api.loginnotice.impl; + +import com.tle.core.guice.Bind; +import com.tle.core.settings.loginnotice.LoginNoticeService; +import com.tle.web.api.loginnotice.LoginNoticeResource; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.core.Response; + +@Bind(LoginNoticeResource.class) +@Singleton +public class LoginNoticeResourceImpl implements LoginNoticeResource +{ + @Inject + LoginNoticeService noticeService; + + @Override + public Response retrieveNotice() + { + String loginNotice = noticeService.getNotice(); + return Response.ok(loginNotice, "text/plain").build(); + } + + @Override + public Response setNotice(String loginNotice) + { + noticeService.setNotice(loginNotice); + return Response.ok().build(); + } + + @Override + public Response deleteNotice() + { + noticeService.deleteNotice(); + return Response.ok().build(); + } +} diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/web/remoting/resteasy/RestEasyServlet.java b/Source/Plugins/Core/com.equella.core/src/com/tle/web/remoting/resteasy/RestEasyServlet.java index c0dc3fc39c..90f793da94 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/web/remoting/resteasy/RestEasyServlet.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/web/remoting/resteasy/RestEasyServlet.java @@ -38,6 +38,7 @@ import com.tle.web.api.institution.GdprResource; import com.tle.web.api.item.SelectionApi; import com.tle.web.api.language.LanguageResource; +import com.tle.web.api.loginnotice.LoginNoticeResource; import com.tle.web.api.searches.SearchConfigApi; import com.tle.web.api.users.UserQueryResource; import com.tle.web.remoting.rest.resource.InstitutionSecurityFilter; @@ -120,7 +121,8 @@ public void init(final ServletConfig servletConfig) throws ServletException { classes.add(AclResource.class); registry.addResourceFactory(new BeanLocatorResource(NewUIThemeResource.class, coreLocator)); classes.add(NewUIThemeResource.class); - + registry.addResourceFactory(new BeanLocatorResource(LoginNoticeResource.class, coreLocator)); + classes.add(LoginNoticeResource.class); registry.addSingletonResource(new GdprResource()); classes.add(GdprResource.class); registry.addSingletonResource(new LegacyContentApi());