From 8d409b9aeee2275381bda13c632b14e527a8c8b0 Mon Sep 17 00:00:00 2001 From: Armstrong Goes Date: Mon, 21 Dec 2020 17:09:03 -0300 Subject: [PATCH] Adding operation that changes the used MembershipService type --- .../fogbow/ms/api/http/request/Admin.java | 11 +++++ .../fogbow/ms/api/parameters/Service.java | 9 ++++ .../fogbow/ms/constants/ApiDocumentation.java | 1 + .../cloud/fogbow/ms/constants/Messages.java | 1 + .../fogbow/ms/core/ApplicationFacade.java | 24 ++++++++++ .../fogbow/ms/core/PluginInstantiator.java | 12 ++++- .../fogbow/ms/core/ApplicationFacadeTest.java | 48 ++++++++++++++++++- 7 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 src/main/java/cloud/fogbow/ms/api/parameters/Service.java diff --git a/src/main/java/cloud/fogbow/ms/api/http/request/Admin.java b/src/main/java/cloud/fogbow/ms/api/http/request/Admin.java index 0f0f397..1a546b4 100644 --- a/src/main/java/cloud/fogbow/ms/api/http/request/Admin.java +++ b/src/main/java/cloud/fogbow/ms/api/http/request/Admin.java @@ -12,6 +12,7 @@ import cloud.fogbow.common.exceptions.FogbowException; import cloud.fogbow.ms.api.http.CommonKeys; import cloud.fogbow.ms.api.parameters.Provider; +import cloud.fogbow.ms.api.parameters.Service; import cloud.fogbow.ms.constants.ApiDocumentation; import cloud.fogbow.ms.constants.SystemConstants; import cloud.fogbow.ms.core.ApplicationFacade; @@ -38,6 +39,16 @@ public ResponseEntity reload( return new ResponseEntity<>(HttpStatus.OK); } + @ApiOperation(value = ApiDocumentation.Admin.SERVICE) + @RequestMapping(value = "/service", method = RequestMethod.POST) + public ResponseEntity service( + @ApiParam(value = cloud.fogbow.common.constants.ApiDocumentation.Token.SYSTEM_USER_TOKEN) + @RequestHeader(required = false, value = CommonKeys.SYSTEM_USER_TOKEN_HEADER_KEY)String systemUserToken, + @RequestBody Service service) throws FogbowException { + ApplicationFacade.getInstance().updateMembershipService(systemUserToken, service.getClassName()); + return new ResponseEntity<>(HttpStatus.OK); + } + @ApiOperation(value = ApiDocumentation.Admin.ADD_PROVIDER) @RequestMapping(value = "/provider", method = RequestMethod.POST) public ResponseEntity addProvider( diff --git a/src/main/java/cloud/fogbow/ms/api/parameters/Service.java b/src/main/java/cloud/fogbow/ms/api/parameters/Service.java new file mode 100644 index 0000000..b0e20c7 --- /dev/null +++ b/src/main/java/cloud/fogbow/ms/api/parameters/Service.java @@ -0,0 +1,9 @@ +package cloud.fogbow.ms.api.parameters; + +public class Service { + private String className; + + public String getClassName() { + return className; + } +} diff --git a/src/main/java/cloud/fogbow/ms/constants/ApiDocumentation.java b/src/main/java/cloud/fogbow/ms/constants/ApiDocumentation.java index 47b47c3..a8fc8cd 100644 --- a/src/main/java/cloud/fogbow/ms/constants/ApiDocumentation.java +++ b/src/main/java/cloud/fogbow/ms/constants/ApiDocumentation.java @@ -40,6 +40,7 @@ public static class Admin { + "to authorized operations from other providers"; public static final String REMOVE_REQUESTER = "Removes given provider from the list of requesters, used by the MembershipService " + "to authorized operations from other providers"; + public static final String SERVICE = "Changes membership service plugin to the given class name"; } } diff --git a/src/main/java/cloud/fogbow/ms/constants/Messages.java b/src/main/java/cloud/fogbow/ms/constants/Messages.java index b8e9913..d7296f0 100644 --- a/src/main/java/cloud/fogbow/ms/constants/Messages.java +++ b/src/main/java/cloud/fogbow/ms/constants/Messages.java @@ -19,6 +19,7 @@ public static class Log { public static final String ADDING_NEW_PROVIDER = "Adding provider: %s"; public static final String ADDING_REQUESTER_PROVIDER = "Adding requester: %s"; public static final String ADDING_TARGET_PROVIDER = "Adding target: %s"; + public static final String CHANGING_MEMBERSHIP_PLUGIN = "Changing membership plugin to: %s"; public static final String GET_PUBLIC_KEY = "Get public key received."; public static final String INTERNAL_SERVER_ERROR = "Internal server error."; public static final String RELOADING_AUTHORIZATION_PLUGIN = "Reloading authorization plugin."; diff --git a/src/main/java/cloud/fogbow/ms/core/ApplicationFacade.java b/src/main/java/cloud/fogbow/ms/core/ApplicationFacade.java index 90d03d1..8fbaece 100644 --- a/src/main/java/cloud/fogbow/ms/core/ApplicationFacade.java +++ b/src/main/java/cloud/fogbow/ms/core/ApplicationFacade.java @@ -15,6 +15,7 @@ import cloud.fogbow.common.plugins.authorization.AuthorizationPlugin; import cloud.fogbow.common.util.CryptoUtil; import cloud.fogbow.common.util.ServiceAsymmetricKeysHolder; +import cloud.fogbow.ms.constants.ConfigurationPropertyKeys; import cloud.fogbow.ms.constants.Messages; import cloud.fogbow.ms.core.authorization.AdminOperation; @@ -85,6 +86,10 @@ public void setMembershipService(MembershipService membershipService) { this.membershipService = membershipService; } + public MembershipService getMembershipService() { + return membershipService; + } + public void setAuthorizationPlugin(AuthorizationPlugin authorizationPlugin) { this.authorizationPlugin = authorizationPlugin; } @@ -231,6 +236,25 @@ private void doReload() throws ConfigurationErrorException { } } + public void updateMembershipService(String userToken, String className) throws FogbowException { + RSAPublicKey asPublicKey = MSPublicKeysHolder.getInstance().getAsPublicKey(); + SystemUser systemUser = AuthenticationUtil.authenticate(asPublicKey, userToken); + this.authorizationPlugin.isAuthorized(systemUser, new AdminOperation()); + + setAsReloading(); + + try { + LOGGER.info(String.format(Messages.Log.CHANGING_MEMBERSHIP_PLUGIN, className)); + MembershipService membershipService = PluginInstantiator.getMembershipService(className); + setMembershipService(membershipService); + + PropertiesHolder.getInstance().setProperty(ConfigurationPropertyKeys.MEMBERSHIP_SERVICE_CLASS_KEY, className); + PropertiesHolder.getInstance().updatePropertiesFile(); + } finally { + finishReloading(); + } + } + private void setAsReloading() { this.reloading = true; } diff --git a/src/main/java/cloud/fogbow/ms/core/PluginInstantiator.java b/src/main/java/cloud/fogbow/ms/core/PluginInstantiator.java index 03f3e46..8ccc070 100644 --- a/src/main/java/cloud/fogbow/ms/core/PluginInstantiator.java +++ b/src/main/java/cloud/fogbow/ms/core/PluginInstantiator.java @@ -13,7 +13,7 @@ public class PluginInstantiator { public static MembershipService getMembershipService() throws ConfigurationErrorException { if (PropertiesHolder.getInstance().getProperties().containsKey(ConfigurationPropertyKeys.MEMBERSHIP_SERVICE_CLASS_KEY)) { String className = PropertiesHolder.getInstance().getProperty(ConfigurationPropertyKeys.MEMBERSHIP_SERVICE_CLASS_KEY); - return (MembershipService) PluginInstantiator.classFactory.createPluginInstance(className); + return getMembershipService(className); } else { return new AllowList(); } @@ -22,10 +22,18 @@ public static MembershipService getMembershipService() throws ConfigurationError public static AuthorizationPlugin getAuthorizationPlugin() throws ConfigurationErrorException { if (PropertiesHolder.getInstance().getProperties().containsKey(ConfigurationPropertyKeys.AUTHORIZATION_PLUGIN_CLASS_KEY)) { String className = PropertiesHolder.getInstance().getProperty(ConfigurationPropertyKeys.AUTHORIZATION_PLUGIN_CLASS_KEY); - return (AuthorizationPlugin) PluginInstantiator.classFactory.createPluginInstance(className); + return getAuthorizationPlugin(className); } else { return new AdminAuthorizationPlugin(); } } + + public static AuthorizationPlugin getAuthorizationPlugin(String className) { + return (AuthorizationPlugin) PluginInstantiator.classFactory.createPluginInstance(className); + } + + public static MembershipService getMembershipService(String className) { + return (MembershipService) PluginInstantiator.classFactory.createPluginInstance(className); + } } diff --git a/src/test/java/cloud/fogbow/ms/core/ApplicationFacadeTest.java b/src/test/java/cloud/fogbow/ms/core/ApplicationFacadeTest.java index 576eaad..1208f54 100644 --- a/src/test/java/cloud/fogbow/ms/core/ApplicationFacadeTest.java +++ b/src/test/java/cloud/fogbow/ms/core/ApplicationFacadeTest.java @@ -22,12 +22,14 @@ import cloud.fogbow.common.models.SystemUser; import cloud.fogbow.common.plugins.authorization.AuthorizationPlugin; import cloud.fogbow.common.util.PublicKeysHolder; +import cloud.fogbow.ms.constants.ConfigurationPropertyKeys; import cloud.fogbow.ms.core.authorization.AdminAuthorizationPlugin; import cloud.fogbow.ms.core.authorization.AdminOperation; @RunWith(PowerMockRunner.class) @PrepareForTest({AuthenticationUtil.class, MSPublicKeysHolder.class, - PropertiesHolder.class, PublicKeysHolder.class}) + PropertiesHolder.class, PublicKeysHolder.class, + PluginInstantiator.class }) public class ApplicationFacadeTest { private ApplicationFacade facade; @@ -41,10 +43,13 @@ public class ApplicationFacadeTest { private String userId = "userId"; private String userName = "userName"; private String provider = "provider"; + + private String newMembershipServiceClassName = "newclass"; private SystemUser systemUser; private RSAPublicKey key; private AdminOperation operation; + private PropertiesHolder propertiesHolder; @Before public void setUp() throws FogbowException { @@ -74,6 +79,47 @@ public void setUp() throws FogbowException { this.facade.setAuthorizationPlugin(authorizationPlugin); } + // test case: When invoking the updateMembershipService method, it must + // authorize the operation, call the PluginInstantiator to get a new + // MembershipService instance and set this instance as the one used + // by the facade. Also, the configuration file must be updated. + @Test + public void testUpdateMembershipService() throws FogbowException { + // set up + MembershipService membershipService = Mockito.mock(MembershipService.class); + + PowerMockito.mockStatic(PropertiesHolder.class); + this.propertiesHolder = Mockito.mock(PropertiesHolder.class); + BDDMockito.given(PropertiesHolder.getInstance()).willReturn(this.propertiesHolder); + + PowerMockito.mockStatic(PluginInstantiator.class); + BDDMockito.given(PluginInstantiator.getMembershipService(newMembershipServiceClassName)).willReturn(membershipService); + + + this.facade.updateMembershipService(token, newMembershipServiceClassName); + + + // verify membership service is updated + assertEquals(this.facade.getMembershipService(), membershipService); + + // verify authorization + Mockito.verify(authorizationPlugin, Mockito.times(1)).isAuthorized(systemUser, operation); + + // verify configuration is update + Mockito.verify(propertiesHolder, Mockito.times(1)).setProperty(ConfigurationPropertyKeys.MEMBERSHIP_SERVICE_CLASS_KEY, + newMembershipServiceClassName); + Mockito.verify(propertiesHolder, Mockito.times(1)).updatePropertiesFile(); + } + + // test case: When invoking the updateMembershipService and the operation + // is not authorized, it must throw an UnauthorizedRequestException. + @Test(expected = UnauthorizedRequestException.class) + public void testUpdateMembershipServiceUnauthorizedOperation() throws FogbowException { + Mockito.doThrow(new UnauthorizedRequestException()).when(this.authorizationPlugin).isAuthorized(systemUser, operation); + + this.facade.updateMembershipService(token, newMembershipServiceClassName); + } + // test case: When invoking the listMembers method, it // must call the listMembers method of the MembershipService // instance it holds and return a list containing the same