From 506a1fc1252268fa31ba89882ea55b7665579965 Mon Sep 17 00:00:00 2001 From: DUVAL Thomas Date: Thu, 16 Jun 2016 14:49:55 +0200 Subject: [PATCH] Delete old files Change-Id: I35cf053f404ba4134eeef46ef177259340634d4f --- odl-aaa-moon/README.md | 64 - odl-aaa-moon/README.rst | 5 - odl-aaa-moon/aaa-authn-api/pom.xml | 38 - odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile | 29 - .../aaa-authn-api/src/main/docs/class_diagram.png | Bin 30016 -> 0 bytes .../aaa-authn-api/src/main/docs/class_diagram.ucls | 127 -- .../src/main/docs/credential_auth_sequence.png | Bin 29197 -> 0 bytes .../src/main/docs/credential_auth_sequence.wsd | 18 - .../src/main/docs/federated_auth_sequence.png | Bin 40566 -> 0 bytes .../src/main/docs/federated_auth_sequence.wsd | 24 - .../aaa-authn-api/src/main/docs/mapping.rst | 1609 ------------------- .../src/main/docs/resource_access_sequence.png | Bin 38693 -> 0 bytes .../src/main/docs/resource_access_sequence.wsd | 25 - .../aaa-authn-api/src/main/docs/sssd_01.diag | 6 - .../aaa-authn-api/src/main/docs/sssd_01.svg | 32 - .../aaa-authn-api/src/main/docs/sssd_02.diag | 18 - .../aaa-authn-api/src/main/docs/sssd_02.svg | 79 - .../aaa-authn-api/src/main/docs/sssd_03.diag | 31 - .../aaa-authn-api/src/main/docs/sssd_03.svg | 143 -- .../aaa-authn-api/src/main/docs/sssd_04.diag | 25 - .../aaa-authn-api/src/main/docs/sssd_04.svg | 100 -- .../aaa-authn-api/src/main/docs/sssd_05.svg | 613 ------- .../src/main/docs/sssd_auth_sequence.png | Bin 39322 -> 0 bytes .../src/main/docs/sssd_auth_sequence.wsd | 23 - .../src/main/docs/sssd_configuration.rst | 1687 -------------------- .../org/opendaylight/aaa/api/Authentication.java | 26 - .../aaa/api/AuthenticationException.java | 31 - .../aaa/api/AuthenticationService.java | 42 - .../main/java/org/opendaylight/aaa/api/Claim.java | 56 - .../java/org/opendaylight/aaa/api/ClaimAuth.java | 37 - .../org/opendaylight/aaa/api/ClientService.java | 20 - .../org/opendaylight/aaa/api/CredentialAuth.java | 28 - .../java/org/opendaylight/aaa/api/Credentials.java | 15 - .../opendaylight/aaa/api/IDMStoreException.java | 24 - .../org/opendaylight/aaa/api/IDMStoreUtil.java | 40 - .../java/org/opendaylight/aaa/api/IIDMStore.java | 72 - .../java/org/opendaylight/aaa/api/IdMService.java | 39 - .../opendaylight/aaa/api/PasswordCredentials.java | 20 - .../org/opendaylight/aaa/api/SHA256Calculator.java | 83 - .../java/org/opendaylight/aaa/api/TokenAuth.java | 37 - .../java/org/opendaylight/aaa/api/TokenStore.java | 25 - .../java/org/opendaylight/aaa/api/model/Claim.java | 60 - .../org/opendaylight/aaa/api/model/Domain.java | 86 - .../org/opendaylight/aaa/api/model/Domains.java | 34 - .../java/org/opendaylight/aaa/api/model/Grant.java | 86 - .../org/opendaylight/aaa/api/model/Grants.java | 35 - .../org/opendaylight/aaa/api/model/IDMError.java | 61 - .../java/org/opendaylight/aaa/api/model/Role.java | 86 - .../java/org/opendaylight/aaa/api/model/Roles.java | 34 - .../java/org/opendaylight/aaa/api/model/User.java | 126 -- .../org/opendaylight/aaa/api/model/UserPwd.java | 40 - .../java/org/opendaylight/aaa/api/model/Users.java | 34 - .../org/opendaylight/aaa/api/model/Version.java | 49 - odl-aaa-moon/aaa-authn-basic/pom.xml | 76 - .../java/org/opendaylight/aaa/basic/Activator.java | 31 - .../org/opendaylight/aaa/basic/HttpBasicAuth.java | 129 -- .../opendaylight/aaa/basic/HttpBasicAuthTest.java | 102 -- odl-aaa-moon/aaa-authn-federation/pom.xml | 132 -- .../org/opendaylight/aaa/federation/Activator.java | 51 - .../aaa/federation/ClaimAuthFilter.java | 249 --- .../aaa/federation/FederationConfiguration.java | 95 -- .../aaa/federation/FederationEndpoint.java | 149 -- .../aaa/federation/ServiceLocator.java | 83 - .../opendaylight/aaa/federation/SssdFilter.java | 151 -- .../OSGI-INF/metatype/metatype.properties | 11 - .../main/resources/OSGI-INF/metatype/metatype.xml | 19 - .../src/main/resources/WEB-INF/web.xml | 34 - .../src/main/resources/federation.cfg | 3 - .../aaa/federation/FederationEndpointTest.java | 121 -- odl-aaa-moon/aaa-authn-keystone/pom.xml | 106 -- .../org/opendaylight/aaa/keystone/Activator.java | 34 - .../aaa/keystone/KeystoneTokenAuth.java | 39 - .../aaa-authn-mdsal-api/pom.xml | 99 -- .../src/main/yang/aaa-authn-model.yang | 154 -- .../aaa-authn-mdsal-config/pom.xml | 40 - .../src/main/resources/initial/08-authn-config.xml | 43 - .../aaa-authn-mdsal-store-impl/pom.xml | 169 -- .../aaa/authn/mdsal/store/AuthNStore.java | 263 --- .../aaa/authn/mdsal/store/DataEncrypter.java | 101 -- .../aaa/authn/mdsal/store/IDMMDSALStore.java | 483 ------ .../aaa/authn/mdsal/store/IDMObject2MDSAL.java | 224 --- .../aaa/authn/mdsal/store/IDMStore.java | 182 --- .../aaa/authn/mdsal/store/util/AuthNStoreUtil.java | 140 -- .../mdsal/store/rev141031/AuthNStoreModule.java | 90 -- .../store/rev141031/AuthNStoreModuleFactory.java | 46 - .../src/main/yang/aaa-authn-mdsal-store-cfg.yang | 77 - .../authn/mdsal/store/DataBrokerReadMocker.java | 112 -- .../aaa/authn/mdsal/store/DataEncrypterTest.java | 38 - .../aaa/authn/mdsal/store/IDMStoreTest.java | 175 -- .../aaa/authn/mdsal/store/IDMStoreTestUtil.java | 181 --- .../aaa/authn/mdsal/store/MDSALConvertTest.java | 78 - .../authn/mdsal/store/util/AuthNStoreUtilTest.java | 88 - odl-aaa-moon/aaa-authn-mdsal-store/pom.xml | 22 - odl-aaa-moon/aaa-authn-sssd/pom.xml | 88 - .../java/org/opendaylight/aaa/sssd/Activator.java | 28 - .../org/opendaylight/aaa/sssd/SssdClaimAuth.java | 220 --- odl-aaa-moon/aaa-authn-store/pom.xml | 100 -- .../java/org/opendaylight/aaa/store/Activator.java | 45 - .../opendaylight/aaa/store/DefaultTokenStore.java | 154 -- .../OSGI-INF/metatype/metatype.properties | 14 - .../main/resources/OSGI-INF/metatype/metatype.xml | 22 - .../aaa-authn-store/src/main/resources/tokens.cfg | 4 - .../aaa/store/DefaultTokenStoreTest.java | 66 - odl-aaa-moon/aaa-authn-sts/pom.xml | 112 -- .../java/org/opendaylight/aaa/sts/Activator.java | 207 --- .../aaa/sts/AnonymousPasswordValidator.java | 30 - .../aaa/sts/AnonymousRefreshTokenValidator.java | 29 - .../org/opendaylight/aaa/sts/OAuthRequest.java | 42 - .../org/opendaylight/aaa/sts/ServiceLocator.java | 141 -- .../org/opendaylight/aaa/sts/TokenAuthFilter.java | 148 -- .../org/opendaylight/aaa/sts/TokenEndpoint.java | 242 --- .../src/main/resources/WEB-INF/web.xml | 23 - .../java/org/opendaylight/aaa/sts/RestFixture.java | 34 - .../org/opendaylight/aaa/sts/TokenAuthTest.java | 94 -- .../opendaylight/aaa/sts/TokenEndpointTest.java | 164 -- odl-aaa-moon/aaa-authn/pom.xml | 103 -- .../main/java/org/opendaylight/aaa/Activator.java | 51 - .../opendaylight/aaa/AuthenticationBuilder.java | 122 -- .../opendaylight/aaa/AuthenticationManager.java | 77 - .../java/org/opendaylight/aaa/ClaimBuilder.java | 160 -- .../java/org/opendaylight/aaa/ClientManager.java | 88 - .../main/java/org/opendaylight/aaa/EqualUtil.java | 42 - .../java/org/opendaylight/aaa/HashCodeUtil.java | 104 -- .../aaa/PasswordCredentialBuilder.java | 87 - .../org/opendaylight/aaa/SecureBlockingQueue.java | 258 --- .../OSGI-INF/metatype/metatype.properties | 12 - .../main/resources/OSGI-INF/metatype/metatype.xml | 16 - .../aaa-authn/src/main/resources/authn.cfg | 2 - .../aaa/AuthenticationBuilderTest.java | 129 -- .../aaa/AuthenticationManagerTest.java | 133 -- .../org/opendaylight/aaa/ClaimBuilderTest.java | 208 --- .../org/opendaylight/aaa/ClientManagerTest.java | 70 - .../opendaylight/aaa/PasswordCredentialTest.java | 39 - .../opendaylight/aaa/SecureBlockingQueueTest.java | 191 --- odl-aaa-moon/aaa-authz/aaa-authz-config/pom.xml | 43 - .../src/main/resources/initial/08-authz-config.xml | 60 - odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml | 95 -- .../src/main/yang/authorization-schema.yang | 190 --- .../aaa-authz/aaa-authz-restconf-config/pom.xml | 43 - .../main/resources/initial/09-rest-connector.xml | 42 - odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml | 152 -- .../aaa/authz/srv/AuthzBrokerImpl.java | 150 -- .../aaa/authz/srv/AuthzConsumerContextImpl.java | 46 - .../authz/srv/AuthzDataReadWriteTransaction.java | 129 -- .../aaa/authz/srv/AuthzDomDataBroker.java | 100 -- .../aaa/authz/srv/AuthzProviderContextImpl.java | 47 - .../aaa/authz/srv/AuthzReadOnlyTransaction.java | 69 - .../aaa/authz/srv/AuthzServiceImpl.java | 121 -- .../aaa/authz/srv/AuthzWriteOnlyTransaction.java | 103 -- .../yang/config/aaa_authz/srv/AuthzSrvModule.java | 76 - .../aaa_authz/srv/AuthzSrvModuleFactory.java | 53 - .../src/main/yang/aaa-authz-service-impl.yang | 115 -- .../authz/srv/AuthzConsumerContextImplTest.java | 46 - odl-aaa-moon/aaa-authz/pom.xml | 23 - odl-aaa-moon/aaa-credential-store-api/pom.xml | 22 - .../src/main/yang/credential-model.yang | 47 - odl-aaa-moon/aaa-h2-store/.gitignore | 2 - odl-aaa-moon/aaa-h2-store/pom.xml | 160 -- .../opendaylight/aaa/h2/config/IdmLightConfig.java | 133 -- .../aaa/h2/persistence/AbstractStore.java | 187 --- .../aaa/h2/persistence/DomainStore.java | 166 -- .../aaa/h2/persistence/GrantStore.java | 158 -- .../opendaylight/aaa/h2/persistence/H2Store.java | 316 ---- .../opendaylight/aaa/h2/persistence/RoleStore.java | 151 -- .../aaa/h2/persistence/StoreException.java | 29 - .../opendaylight/aaa/h2/persistence/UserStore.java | 202 --- .../authn/h2/store/rev151128/AAAH2StoreModule.java | 49 - .../store/rev151128/AAAH2StoreModuleFactory.java | 29 - .../resources/initial/08-aaa-h2-store-config.xml | 26 - .../aaa-h2-store/src/main/yang/aaa-h2-store.yang | 28 - .../aaa/h2/persistence/DomainStoreTest.java | 76 - .../aaa/h2/persistence/GrantStoreTest.java | 76 - .../aaa/h2/persistence/H2StoreTest.java | 187 --- .../aaa/h2/persistence/RoleStoreTest.java | 76 - .../aaa/h2/persistence/UserStoreTest.java | 79 - odl-aaa-moon/aaa-idmlight/pom.xml | 229 --- .../opendaylight/aaa/idm/IdmLightApplication.java | 57 - .../org/opendaylight/aaa/idm/IdmLightProxy.java | 208 --- .../org/opendaylight/aaa/idm/StoreBuilder.java | 118 -- .../opendaylight/aaa/idm/rest/DomainHandler.java | 591 ------- .../org/opendaylight/aaa/idm/rest/RoleHandler.java | 228 --- .../org/opendaylight/aaa/idm/rest/UserHandler.java | 419 ----- .../opendaylight/aaa/idm/rest/VersionHandler.java | 46 - .../idmlight/rev151204/AAAIDMLightModule.java | 90 -- .../rev151204/AAAIDMLightModuleFactory.java | 29 - .../src/main/resources/WEB-INF/web.xml | 79 - .../aaa-idmlight/src/main/resources/idmtool.py | 247 --- .../resources/initial/08-aaa-idmlight-config.xml | 26 - .../aaa-idmlight/src/main/yang/aaa-idmlight.yang | 28 - .../aaa/idm/persistence/PasswordHashTest.java | 93 -- odl-aaa-moon/aaa-idmlight/tests/cleardb.sh | 5 - odl-aaa-moon/aaa-idmlight/tests/domain.json | 5 - odl-aaa-moon/aaa-idmlight/tests/domain2.json | 5 - odl-aaa-moon/aaa-idmlight/tests/grant.json | 4 - odl-aaa-moon/aaa-idmlight/tests/grant2.json | 4 - odl-aaa-moon/aaa-idmlight/tests/result.json | 1 - odl-aaa-moon/aaa-idmlight/tests/role-admin.json | 4 - odl-aaa-moon/aaa-idmlight/tests/role-user.json | 4 - odl-aaa-moon/aaa-idmlight/tests/test.sh | 308 ---- odl-aaa-moon/aaa-idmlight/tests/user.json | 7 - odl-aaa-moon/aaa-idmlight/tests/user2.json | 7 - odl-aaa-moon/aaa-idmlight/tests/userpwd.json | 4 - odl-aaa-moon/aaa-idp-mapping/pom.xml | 84 - .../org/opendaylight/aaa/idpmapping/Activator.java | 25 - .../org/opendaylight/aaa/idpmapping/IdpJson.java | 248 --- .../aaa/idpmapping/InvalidRuleException.java | 35 - .../aaa/idpmapping/InvalidTypeException.java | 35 - .../aaa/idpmapping/InvalidValueException.java | 35 - .../opendaylight/aaa/idpmapping/RuleProcessor.java | 1368 ---------------- .../aaa/idpmapping/StatementErrorException.java | 35 - .../org/opendaylight/aaa/idpmapping/Token.java | 401 ----- .../aaa/idpmapping/UndefinedValueException.java | 34 - .../aaa/idpmapping/RuleProcessorTest.java | 130 -- .../org/opendaylight/aaa/idpmapping/TokenTest.java | 66 - odl-aaa-moon/aaa-shiro-act/pom.xml | 84 - .../org/opendaylight/aaa/shiroact/Activator.java | 51 - .../opendaylight/aaa/shiroact/ActivatorTest.java | 25 - odl-aaa-moon/aaa-shiro/pom.xml | 156 -- .../java/org/opendaylight/aaa/shiro/Activator.java | 45 - .../org/opendaylight/aaa/shiro/ServiceProxy.java | 94 -- .../aaa/shiro/accounting/Accounter.java | 38 - .../aaa/shiro/authorization/DefaultRBACRules.java | 78 - .../aaa/shiro/authorization/RBACRule.java | 170 -- .../opendaylight/aaa/shiro/filters/AAAFilter.java | 72 - .../aaa/shiro/filters/MoonOAuthFilter.java | 187 --- .../shiro/filters/ODLHttpAuthenticationFilter.java | 78 - .../opendaylight/aaa/shiro/moon/MoonPrincipal.java | 155 -- .../aaa/shiro/moon/MoonTokenEndpoint.java | 30 - .../aaa-shiro/src/main/resources/WEB-INF/web.xml | 48 - .../aaa-shiro/src/main/resources/shiro.ini | 95 -- .../opendaylight/aaa/shiro/ServiceProxyTest.java | 45 - .../shiro/authorization/DefaultRBACRulesTest.java | 43 - .../aaa/shiro/authorization/RBACRuleTest.java | 106 -- .../aaa/shiro/realm/ODLJndiLdapRealmTest.java | 246 --- .../aaa/shiro/realm/TokenAuthRealmTest.java | 139 -- .../shiro/web/env/KarafIniWebEnvironmentTest.java | 76 - odl-aaa-moon/artifacts/pom.xml | 231 --- odl-aaa-moon/commons/docs/AuthNusecases.vsd | Bin 206336 -> 0 bytes odl-aaa-moon/commons/docs/direct_authn.png | Bin 22058 -> 0 bytes odl-aaa-moon/commons/docs/federated_authn1.png | Bin 36542 -> 0 bytes odl-aaa-moon/commons/docs/federated_authn2.png | Bin 35203 -> 0 bytes odl-aaa-moon/commons/federation/README | 271 ---- .../federation/idp_mapping_rules.json.example | 30 - odl-aaa-moon/commons/federation/jetty.xml.example | 85 - .../commons/federation/my_app.conf.example | 31 - .../AAA_AuthZ_MDSAL.json.postman_collection | 77 - odl-aaa-moon/distribution-karaf/pom.xml | 274 ---- odl-aaa-moon/features/api/pom.xml | 91 -- .../features/api/src/main/features/features.xml | 21 - odl-aaa-moon/features/authn/pom.xml | 304 ---- .../features/authn/src/main/features/features.xml | 247 --- odl-aaa-moon/features/authz/pom.xml | 101 -- .../features/authz/src/main/features/features.xml | 31 - odl-aaa-moon/features/pom.xml | 19 - odl-aaa-moon/features/shiro/pom.xml | 179 --- .../features/shiro/src/main/features/features.xml | 41 - odl-aaa-moon/parent/pom.xml | 278 ---- odl-aaa-moon/pom.xml | 50 - 258 files changed, 28007 deletions(-) delete mode 100644 odl-aaa-moon/README.md delete mode 100644 odl-aaa-moon/README.rst delete mode 100644 odl-aaa-moon/aaa-authn-api/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.png delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.ucls delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.png delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.wsd delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.png delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.wsd delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/mapping.rst delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.png delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.wsd delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.diag delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.svg delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.diag delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.svg delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.diag delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.svg delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.diag delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.svg delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_05.svg delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.png delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.wsd delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_configuration.rst delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Authentication.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationException.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationService.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Claim.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClaimAuth.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClientService.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/CredentialAuth.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Credentials.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreException.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreUtil.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IIDMStore.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IdMService.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/PasswordCredentials.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/SHA256Calculator.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenAuth.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenStore.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Claim.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domain.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domains.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grant.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grants.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/IDMError.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Role.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Roles.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/User.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/UserPwd.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Users.java delete mode 100644 odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Version.java delete mode 100644 odl-aaa-moon/aaa-authn-basic/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java delete mode 100644 odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java delete mode 100644 odl-aaa-moon/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java delete mode 100644 odl-aaa-moon/aaa-authn-federation/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/Activator.java delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ClaimAuthFilter.java delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationConfiguration.java delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationEndpoint.java delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ServiceLocator.java delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/SssdFilter.java delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.properties delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.xml delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/resources/WEB-INF/web.xml delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/main/resources/federation.cfg delete mode 100644 odl-aaa-moon/aaa-authn-federation/src/test/java/org/opendaylight/aaa/federation/FederationEndpointTest.java delete mode 100644 odl-aaa-moon/aaa-authn-keystone/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/Activator.java delete mode 100644 odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/KeystoneTokenAuth.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/src/main/yang/aaa-authn-model.yang delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/src/main/resources/initial/08-authn-config.xml delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/AuthNStore.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypter.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMMDSALStore.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMObject2MDSAL.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMStore.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtil.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/mdsal/store/rev141031/AuthNStoreModule.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/mdsal/store/rev141031/AuthNStoreModuleFactory.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/yang/aaa-authn-mdsal-store-cfg.yang delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataBrokerReadMocker.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypterTest.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTest.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTestUtil.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/MDSALConvertTest.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtilTest.java delete mode 100644 odl-aaa-moon/aaa-authn-mdsal-store/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-sssd/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/Activator.java delete mode 100644 odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/SssdClaimAuth.java delete mode 100644 odl-aaa-moon/aaa-authn-store/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/Activator.java delete mode 100644 odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/DefaultTokenStore.java delete mode 100644 odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.properties delete mode 100644 odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.xml delete mode 100644 odl-aaa-moon/aaa-authn-store/src/main/resources/tokens.cfg delete mode 100644 odl-aaa-moon/aaa-authn-store/src/test/java/org/opendaylight/aaa/store/DefaultTokenStoreTest.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/Activator.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousPasswordValidator.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousRefreshTokenValidator.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/OAuthRequest.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/ServiceLocator.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenAuthFilter.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenEndpoint.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/main/resources/WEB-INF/web.xml delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/RestFixture.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenAuthTest.java delete mode 100644 odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenEndpointTest.java delete mode 100644 odl-aaa-moon/aaa-authn/pom.xml delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java delete mode 100644 odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties delete mode 100644 odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml delete mode 100644 odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg delete mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java delete mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java delete mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java delete mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java delete mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java delete mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-config/pom.xml delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-config/src/main/resources/initial/08-authz-config.xml delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-model/src/main/yang/authorization-schema.yang delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/pom.xml delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/src/main/resources/initial/09-rest-connector.xml delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzBrokerImpl.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImpl.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDataReadWriteTransaction.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDomDataBroker.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzProviderContextImpl.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzReadOnlyTransaction.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzServiceImpl.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzWriteOnlyTransaction.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModule.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModuleFactory.java delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/yang/aaa-authz-service-impl.yang delete mode 100644 odl-aaa-moon/aaa-authz/aaa-authz-service/src/test/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImplTest.java delete mode 100644 odl-aaa-moon/aaa-authz/pom.xml delete mode 100644 odl-aaa-moon/aaa-credential-store-api/pom.xml delete mode 100644 odl-aaa-moon/aaa-credential-store-api/src/main/yang/credential-model.yang delete mode 100644 odl-aaa-moon/aaa-h2-store/.gitignore delete mode 100644 odl-aaa-moon/aaa-h2-store/pom.xml delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/config/IdmLightConfig.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/AbstractStore.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/DomainStore.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/GrantStore.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/H2Store.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/RoleStore.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/StoreException.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/UserStore.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModule.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModuleFactory.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/resources/initial/08-aaa-h2-store-config.xml delete mode 100644 odl-aaa-moon/aaa-h2-store/src/main/yang/aaa-h2-store.yang delete mode 100644 odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/DomainStoreTest.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/GrantStoreTest.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/H2StoreTest.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/RoleStoreTest.java delete mode 100644 odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/UserStoreTest.java delete mode 100644 odl-aaa-moon/aaa-idmlight/pom.xml delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml delete mode 100644 odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang delete mode 100644 odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/cleardb.sh delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/domain.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/domain2.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/grant.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/grant2.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/result.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/role-admin.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/role-user.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/test.sh delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/user.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/user2.json delete mode 100644 odl-aaa-moon/aaa-idmlight/tests/userpwd.json delete mode 100644 odl-aaa-moon/aaa-idp-mapping/pom.xml delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Activator.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/IdpJson.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidRuleException.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidTypeException.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidValueException.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/StatementErrorException.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Token.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/UndefinedValueException.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/RuleProcessorTest.java delete mode 100644 odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/TokenTest.java delete mode 100644 odl-aaa-moon/aaa-shiro-act/pom.xml delete mode 100644 odl-aaa-moon/aaa-shiro-act/src/main/java/org/opendaylight/aaa/shiroact/Activator.java delete mode 100644 odl-aaa-moon/aaa-shiro-act/src/test/java/org/opendaylight/aaa/shiroact/ActivatorTest.java delete mode 100644 odl-aaa-moon/aaa-shiro/pom.xml delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/resources/WEB-INF/web.xml delete mode 100644 odl-aaa-moon/aaa-shiro/src/main/resources/shiro.ini delete mode 100644 odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java delete mode 100644 odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java delete mode 100644 odl-aaa-moon/artifacts/pom.xml delete mode 100644 odl-aaa-moon/commons/docs/AuthNusecases.vsd delete mode 100644 odl-aaa-moon/commons/docs/direct_authn.png delete mode 100644 odl-aaa-moon/commons/docs/federated_authn1.png delete mode 100644 odl-aaa-moon/commons/docs/federated_authn2.png delete mode 100644 odl-aaa-moon/commons/federation/README delete mode 100644 odl-aaa-moon/commons/federation/idp_mapping_rules.json.example delete mode 100644 odl-aaa-moon/commons/federation/jetty.xml.example delete mode 100644 odl-aaa-moon/commons/federation/my_app.conf.example delete mode 100644 odl-aaa-moon/commons/postman_examples/AAA_AuthZ_MDSAL.json.postman_collection delete mode 100644 odl-aaa-moon/distribution-karaf/pom.xml delete mode 100644 odl-aaa-moon/features/api/pom.xml delete mode 100644 odl-aaa-moon/features/api/src/main/features/features.xml delete mode 100644 odl-aaa-moon/features/authn/pom.xml delete mode 100644 odl-aaa-moon/features/authn/src/main/features/features.xml delete mode 100644 odl-aaa-moon/features/authz/pom.xml delete mode 100644 odl-aaa-moon/features/authz/src/main/features/features.xml delete mode 100644 odl-aaa-moon/features/pom.xml delete mode 100644 odl-aaa-moon/features/shiro/pom.xml delete mode 100644 odl-aaa-moon/features/shiro/src/main/features/features.xml delete mode 100644 odl-aaa-moon/parent/pom.xml delete mode 100644 odl-aaa-moon/pom.xml diff --git a/odl-aaa-moon/README.md b/odl-aaa-moon/README.md deleted file mode 100644 index 71f52a63..00000000 --- a/odl-aaa-moon/README.md +++ /dev/null @@ -1,64 +0,0 @@ -## Welcome to the OPNFV/Opendaylight AAA Project! - -This project is aimed at providing a flexible, pluggable framework with out-of-the-box capabilities for: - -* *Authentication*: Means to authenticate the identity of both human and machine users (direct or federated). -* *Authorization*: Means to authorize human or machine user access to resources including RPCs, notification subscriptions, and subsets of the datatree. -* *Accounting*: Means to record and access the records of human or machine user access to resources including RPCs, notifications, and subsets of the datatree - - - -### Building - -*Prerequisite:* The followings are required for building AAA: - -- Maven 3 -- Java 7 - -Get the code: - - clone the project with git - -Build it: - - cd aaa && mvn clean install -DskipTests - -### Export Moon information - -export MOON_SERVER_ADDR=192.168.105.135 -export MOON_SERVER_PORT=5000 - - -### Installing - -AAA installs into an existing Opendaylight controller Karaf installation. If you don't have an Opendaylight installation, please refer to this [page](https://wiki.opendaylight.org/view/OpenDaylight_Controller:Installation). - -Start the controller Karaf container: - cd distribution-karaf/target/assembly/ - bin/karaf - -Install AAA AuthN features: - - feature:install odl-aaa-shiro - -### Running - -Once the installation finishes, one can authenticates with the Opendaylight controller by presenting a username/password and a domain name (scope) to be logged into: - - curl -s -d 'grant_type=password&username=admin&password=admin&scope=sdn' http://:/moon/token - -Upon successful authentication, the controller returns an access token with a configurable expiration in seconds, something similar to the followings: - - {"expires_in":3600,"token_type":"Bearer","access_token":"d772d85e-34c7-3099-bea5-cfafd3c747cb"} - -The access token can then be used to access protected resources on the controller by passing it along in the standard HTTP Authorization header with the resource request. Example: - - curl -s -H 'Authorization: Bearer d772d85e-34c7-3099-bea5-cfafd3c747cb' http://:/restconf/operational/opendaylight-inventory:nodes - -The operational state of access tokens cached in the MD-SAL can also be obtained after enabling the restconf feature: - - feature:install odl-aaa-all - -At the following URL - - http://controller:8181/restconf/operational/aaa-authn-model:tokencache/ diff --git a/odl-aaa-moon/README.rst b/odl-aaa-moon/README.rst deleted file mode 100644 index 2ef4b3b7..00000000 --- a/odl-aaa-moon/README.rst +++ /dev/null @@ -1,5 +0,0 @@ -OpenDaylight AAA for Moon -========================= - -This is an version of OpenDaylight AAA for Moon - diff --git a/odl-aaa-moon/aaa-authn-api/pom.xml b/odl-aaa-moon/aaa-authn-api/pom.xml deleted file mode 100644 index 31a17236..00000000 --- a/odl-aaa-moon/aaa-authn-api/pom.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn-api - bundle - - - - org.slf4j - slf4j-api - - - org.slf4j - slf4j-simple - - - com.sun.jersey - jersey-server - provided - - - - - - org.apache.felix - maven-bundle-plugin - - - - - diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile b/odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile deleted file mode 100644 index 446795b4..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -all: sssd_configuration.html sssd_configuration.pdf mapping.html - - -images = sssd_01.png sssd_02.png sssd_03.png sssd_04.png sssd_05.png - -sssd_configuration.html: $(images) - -sssd_configuration.pdf: $(images) - -%.html: %.rst - rst2html $< $@ - -%.pdf: %.rst - rst2pdf --footer='-###Page###-' $< -o $@ - -%.png: %.svg - inkscape -z -e $@ -w 800 $< - -sssd_01.svg: sssd_01.diag - blockdiag -Tsvg $< - -sssd_02.svg: sssd_02.diag - blockdiag -Tsvg $< - -sssd_03.svg: sssd_03.diag - seqdiag -Tsvg $< - -sssd_04.svg: sssd_04.diag - blockdiag -Tsvg $< diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.png b/odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.png deleted file mode 100644 index 999a41f9b68d60c9f980ce6b0364d434153a573c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30016 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 ZN*F}@^S%<$yNAhr$xAC;&yh0r{x9OgH|GEV diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.ucls b/odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.ucls deleted file mode 100644 index 68345256..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.ucls +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.png b/odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.png deleted file mode 100644 index 52d636503141c50d6a2544a5674dcd2cc5e27edf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29197 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 WctVnGFYspqK}S>XP`ZY>_rC$pz5QeW diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.wsd b/odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.wsd deleted file mode 100644 index 383d4031..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.wsd +++ /dev/null @@ -1,18 +0,0 @@ -title Credential Authentication Sequence - -# This walks through the credential authentication use case where a credential -# (typically username/password) is used to authenticate directly with the ODL -# controller. - -Client -> ServletContainer: request access token -note right of Client -(credentials, scope=domain) -end note -ServletContainer -> TokenEndpoint: credentials, domain -TokenEndpoint -> CredentialAuth: authenticate(Credentials, domain) -CredentialAuth -> TokenEndpoint: Claim -note left of CredentialAuth -(user/domain/roles) -end note -TokenEndpoint -> TokenEndpoint: createToken -TokenEndpoint -> Client: access token \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.png b/odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.png deleted file mode 100644 index 799cc9095681dea6dc3f5709eca7cb710994bb4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40566 zcmcG$bzGHe*DZ`qg8>K#hzKYp-6bd?4bmlzq;xM(2@#MI>F$=6R+R2;knZk2bGe`A zee#cUzH`2{_fHU6>%Q+R<{Wd(F)m+OX;Cb6B6JiK6fAMEXYwd0S1M3YE_0z?hM%CY zbsxf?*L5XCpP^hJ|4XP&4@E(_k0So;=}X6$)o}+6g~1Df&CsT}x1N+AJZ|4m%Eh=w zpmNWc#2D}SO43Iq(~{EC{;Z)9llt8Ir3^2slo4fxGR@e@ZbH38e1n2T&|~oDyT~AhE68t} zJZ%Y)Uj?aBT9IE-n8`J-BfkcrEiog%dPs_eUqXJPFI0hv{CfZP|Cf(=8fRI1_JwlP z_cry*yrY>w0mA|nLqo%oySew=&-Rpcb(5mP`X0?M^``e~Eq5ip=MMPzoZqP9o|?Id zNfvj1*1+-U(a!4?hmDE;9`BT_yu45T{;QLN;jJW`Z+=fyyJTl)M_#7! z59KCOlPoE8-R%}F8H#BM2??pHu9o`q;;~!)wXnlR5-hoABPAvwp;t7lS!s_)qKpy} z#2`#@uszqR^V2)vF4Oeh%HZCBvADRnnHim%+r{}AF^5Hltgpy}{EGoLmk7_GmX?;A z6e4XAOf%E-!ZDp+720kX5+b*l1z-mniJK%bcfow|zlOC0HOvciq;s$s+ zG;(KWXC)=2%Wr?fVCv}Th=_UViB{iX6M%=<%@p0_xN~BJ@*%#qM_mZ zaFG!jIX^%Du7m&Ssq671wO-5T$Gcp--9|UK?qTovC&x5pe*E~ct4o%EfPjJ`xg(Z` zr4#YU#&-9TG5i%qr#+e@U$w{(h8qTStjH+v6cKuS_zD(vr@+NnqVNqpJw0M#VoS>n zSfqZ}lk3nz_F|?Jg?(IIT^)JjU`881kq8S5M`8*_&V*#;O2|sVSOw!H^Sg(E?j$&!DWB- zX!7D*R2%0Wr))9iS@r|+*`u9>HW3L42_dm`>Dbw4q_=Jfxqm*L?b@1Y3|o~{^Nows z8Z9+PBTpi6+@2NZ%I%Eikl7VYlvRKE4%gIcz-)HrwqJdO zi~5ii-uR-Hy)IK`_@G~)Ta3cg5kS&mygKN zyoBZ*2Ay{1eXCPP>?AYB#M3qD8|vz&Ap^DPd_0CHTKa@;^d;x3;!4GJN2vsYOZa|a z(b3tPr|&Ev_ii&T6{f%v>SCr4{ggrwh1{&<QffBLOhdB_fqAztlqjurIsR`G*)8B@Q?6my`u+xvF)ifYJLq1py9~JB zK(;8BTp7qkBOkA@OBO&JZa$SkzVb6N#FT_t)0vo=Lliy8X%E8KCT3TKP`7q=TH)2) z{CY5tWPJMf@1UqCH7zZEi!sg$N^Wj7otNmn2J$~wB4m9-o>$D||))tqBrD)bivp3JwW6-lVU8Xm{2Zv0_ z5%H7*z?ajVv{uW>npB574WCF_SD!zzhxxsB?HWKER$@jrHUq>=t-}Usu4md!blRIA zHS#&kM;;PQK`cDjnCu)=!9(}jjfYEXlHLo4GayHR8^aKZR*C%_#M_`^eK}f6rXV3`;^0s*=&{crm9&y zJTkGK7jtu`Q=pd~*1m1)NQo()NUCIoM(;{QU1*1`9YBK4SRQ31C8|5$`pM{@+^9;}BMWRDDHn}SJ6Nt4tUg$Ta@3vN1jqxxDC6wqoV`n$rBe=)iL9h@iN%!PqeBM1ZZ-e?RI|9 z4dg8a#0CGWs;V+EF>%2DaNK&3TUuBsKTy#30(KkD5nu|L)T`I8GiX+P#sy532V1r0 zgl}2z7*=cN$OtzJOHoS7v&iJzYi;{$qrC)l_!W?H;uP24ieYs^DLOeF z{dj+4$@I9W6Fe!?YcA(KM+g#)Hn_OFa(GWn4fq`-R_U+ddya3TT zEGNbw14DEf%+sXs&m zb+0mbCjiwMlid=EsFTEDsht}ze%F-vXYh>*R73+wvCz?zwAbY1<*m=*Qx-a(Xja%x z*0?2$1cVTYNEv-m8G0#InRiL+cEr?66_pHE%mjt6%-1u#j%FKS-_ss7`ay7F;b339 zD8cO3p2kypP?5Y?5ivJ62eT?ICdSCf7{Otw1!JM5Me5e1(h*h$;c6MM{;f(8za!Yt zOBnODxbs)eAq4L3e+|(mc@@B|(5^l%DLOYh%SbHo;)TUzja1gPmV|`M1-^JO6`|gt_ZNw4fj#tHQ2ceCX zehbT_l^QLRL_M!BlgqwYf#2S8e|Bq23pN?QhO8`r>*lexrk0i@ncw5(HlnNQDwGt; zA~4F>25Js(-h3@9b3Z$_la~(>yr1CXx;j(jDqgq%%;H-pV z0XEdqq#`X1O+*RN@%~zN+f#8U>aPSgLOvCjl;nN$`}^V6%+h{L%soRsLzs$r9;(^h ztjfw0SQ7H`^32T4iwnzuOvnTfbR#i!J6eBYjn1uUq7q#rqsE1u`gOjQl@%N!*2S(w zVL3Uu;w5(Tk=>QSCvSpPn5AnO+zdi!i<4+NpfILj~^&g(egin+Kw!px~?r3k9l954H+KYCwkuOqoNjY717jNdifns2nlyPUg zUh>^G;397?FFrt?urGEY>>nOF3Vi^)pC%QJIIdk>7J~wJ_chi$#!UixHQT*q#i*7K zijN4(--RY>8D$M{YxdWsr2J}aH3bxcti>VVFR4RmDYtf2-24MVKOMrbb}b@XIUgGb z=U{s~$*&x_lGEa7x_Wz~S&XpV_;l*70_y-74KiGb#dy>NtvQ6{Ml$yt$^0I?D0mtl zOu}0vB-OBE=C8B&pHv_F{xkf0*D+TL_0Fez~scgLVX{mM&P9Vi%~4zPsE9WYa^ z^WHN21<-`}Z3S!}Jxj|crj6WDs)ej=rb5on+-~Fx(Ns?d#m_0RJrdh0T473WR))Sn zEhsLovRlEY-#kBaF|;Gk5)Y-?Rcpw(oAC7()sExAI!Ujsq9R_=)d)X7j59ydYC=hY zh$nyW0;SqQfnK8ttsxT(V=^TGX^CcsNXriirkC{%445v=WdU#Rc04Fz_U>zn#BuJm zqkeQ;zCGU_O*Y;q{ zMCNEFZOl|)mDJ`KTEgrN zJXr5>p+T#0BnAOP7ya&u>W8LVWY&+0m_=Yew8ZQJZKq@s_*_J!=d!$VgRG&UIF@=z zo@vD3RpPL$bos6@pqjU#_p2H9BuH&c0)2Adw+iZt>ZmYQ`42ECNbwJh#g7Vxq}c> z6|uir-#j}K z3Q$osG&VMljEpojWeEFVL!N#iCMLUMC4A+|`y?xXx$mN(9`?* zP_QjP->{Gohv7CQ|3V`pD=RWWLWn&(J2N*o-`m}F6W`vpzdLm5%49zwnD)u`m?xXUs?2JxRHaMU>J}Rw+EwaBHAv z!Sxb~pd^Iq(2x*GDXGZLTdzyxl0`_ktRFsl^xeY7;kD0ZzF5kcI z04%WJ7CKv_~yFt z|MygNin8dQNRe}&T)Uk~45N52x!_rMk6c!NvFS%0&%VcNM3{n%6z3G&w%ycriI=qQ zEz@WMeRJD;P;<_XJPI#o2{)nU_4uQHlM&8(Mxo^o?AX`uN8If3W;jlnvWw;w~STEmgAnN92FtIXOQ80vNdNvS;pdPLr5juCI+|Ni4^%05kW^UsVs0ejEq%7fP*)h+Q6f)$ariXbJK32SElw>v?-mE z5kWY)JVv(rQ+Ae}JEpB|Z3@k9XQx5~dE?2h>gdVl_mpwk(_0G)j9lGn7l(^x`m-zL z6=p79@ws}H?xXLKXfbAZt8aj&o{5P@a+Hk?h6$T|mnMZM14Qk!tr=ocUd}4Rc4Z#> zds>D13gc|l+_qtn_t|n4LQE7e@6yDlMeVzaiw{p!Q>ROv*#7iBL&u=C!5aH!wUw>B z)mBR>ktU9X1~+|kE%v6J{z6Cm;vEcZ(%$^Z#$7wz>4^$cCT8o%#XiLLtO^NR%I0Ub zPuV`+y>r{!ABT(Fv9V%z&JKD zl5-#1`r%6poOAAMPVK0wUapW^wI`|Odgz4Z|ABLMnY*v8EtD(Q6PK3OhMJmZKHkeK zb8+v>t!(u7mu7pH3FSn;X-II_x3&3WVPP$ry0|a|+Y=h?Lzf2|Q)7CG>*(wR=h@P! zXwLKF{MQP^Tyrxs`F4wp4SmIfLj{vv(d({=FPX3NH)=)~*Z6YXPP!$+EmGf#i_l)w z28x9GFTE~3BIb~{vEHVZBBH6Vny5l6Y^im>_%V3k7ZhZbE^Y2~xOo&rZgcORBSfJ` zj~=x&H`iL}Xqd^S3st=^+BR=^(+}xbbD5QQ8(^mClZCB9Ve%2zz=6;T0vB( zs!HpK;o|1vI&KanSQ`n5}B)*qBo-?Xh9zCua5!jgn9UhRK!c9PSt8N_;FRy=wz-B@Bu@Q_*n4OQjWl;gFlbV+)W9Uhq2r0p|S z4gOoe@$uSF1>@er+dDYiLR`OoELmc0pFS%S^z9pq`Dlf=r+@F@AasY;>b#!1pKfjB zZ&;@Z_olHQY>*K#CY+ockXc)A!+jMMUDIXrTg%&I%*~}&Tptw?Y+i|A<`Zx`784Q! z47q3uQ86vRFEw@8k6_p{sL}xnnYQ!`0=NPePGIQH)vA=PE?iSn2?qz|C;1FA@ioW! z^>~Jcal6_F>$mZg-u*y9f4%3QVPwrPQ)G1Ex^E`$dWsL@E5CDUbb4qRnQ3k~4_z9f zG`WIp(N7x2lQs38Xz%xSqa>qPi$w!JNj8(Y$2>&^oE^)g*LQxFKKqO|dz*Wu{5Wl@ zvK1Sf9hQgXP5~n3c6WkQ?1Q$V=)|W8CAZ39rVt z?=2obeoV|?wXr$nhDJ_EXsMJp)}24On=T{ML>SZ_HGErW$jn~0>uW|v?G5ZzQev0+ zc=u9YC0Zkg7w1=)6&4;DXLDtZgv%H!fV3`()~A zZoU|hKqdEVJ>$45EPQ3YC@Wj3oscPM(tUI$J$-w`pza*ZthBWK1Ey2rz~JCZHZ_jC*@O8ubI8FX+7l5@o3e%G z_2}@B3JE9flVhVOEaH71pGH1+gnVIDa&m5ErTU8(m}heL9hDUoHPzIpO04nEAEK5_ zEp}tPeJi0=%P;X{04|V7bD7lR?^=LF@|!n(fYhS56YRNBt8x)g`&hx-VTl=9pBSG$ zm6ExSNf#StR^ARyq;-GC$ukZjg3-&q$*C!yJk8O$`T1w4OT7y= zUVI5{I?K$I`Fb?FRT*`4IC66CWL$;o$$=wX^y;P0EXN#Q!Kf|m;NaomiHKbH3>q7A zRTGR1)iW|0FEn8Bs*lKGyemka8+UV0vcurCDcBCe&D9$|6SeLielq3DoeQHDlr7M( zl%SEr4W>(L8X(%*?%1xqKqXI2oon0RU0Qk)%?1UrJGcM!p*5PW&7XIjou%sP@DT^& z4Q!_N0`76m3r1dEYw9(;rwj25J(E=RhbJyZMi>M6fms>A#k#JnoIF<<;#UnLCL&5n zt)`JF&!%L!W-N+GUC0$$96G9}R77`UmSX@X#mw zv69jVA&1O*)B6f|*RMQE!C*-$Z7TOCzPkuedti7N@#Ed3czga&$PBP~o?SFoyytf= z?kqAQ1jv$?lk?G!CpzejkCW-vG!}FW9zT8^%^@li$y_CJvBXI9sw`V@2c!Agb!=bX zqqSeYt+71r0|k15W~)$7OB!YCs+ zj2_krLvt zpf^V6r{DYfY>)V4lbzC|KbL<3*qBuJ6QJA;KMO1S(u1v~0)*S=f`X}1a|w0}-tTk! ze0Iw;J5S@)s;)CD>M(jR6P@{I&~HDPfjXh_Q!1?Acz(CsGzH7!-MRLtBj0miNy>BX zO2>^A+nM^O8pK0RNFp0ADk!kFBjMI>imCTYhyHBw--phFawU?K?Rk+i63ahtx1)~2 zX_0%c3EvoR`ByfbmCnpAlDU6BuWHAyiAi=9OJ#=H6__?b91 zH2q3m@4-8l3itPk7P0;1gK#LW3f?jFd}zLU8*4(-B7(Jw`H$j;^$`Pulvf?k$JQmx zcK1FUWFe98!;0!QAfwX02NpR!oEJ}X+T>_N|ZeC6qSXUS%ppsYH~O4tMDrol7BuhBW-Ll6A5wCnFzUY~xIO3|DVmoi0dcGBYzl;{fWAT=gO9?&3!b`%9rWCh9PZH2?#AQ=C`~+rp!!C;`VIMsJk6tH{`GU-Z{F5y#Vy%8szlkVoqD$E4&s{J0k7h&JOb71Vxg)ktN=CM^yc`u8s-UV`{_4kN zZEfuezu4IENEV}C@0{S1-@g4(ZnLmxdgF%Ac%{R_{Jff~s+X5nSa>)PlHdE{g~W4c-6xK;P-mp&+3pnCv~IG|aUVALUZK1XeCZH;C&cmZ(&vJ^0Osn<78 zk8FgRfM)`M*R{(}d()(#L#nNuUW8zaQbj32O}=b{QQlnDJFQZ4<9~&a-igOFV+3~9=Oc*t3&*5$4yzYz+s0}0;yCB;D(ub z8|Dvw85V;3tYgD-)D!b(E zOMQ>BVBVoW=M#4~b4g`c*+Cqi z%k<1lrR~!6{5(D$9wjxVo7)*Qkf2PkTkO&~+Mav<{JBu#Cg|CurKKe!1No0X{G1XS z&u~Ry`?w6uHgKJ7f$#^YAX4%HQ>n_zzL~1KcAVUYvZ&5W8VI;CMcmG=BxW9YcPTx+ zwkT}9P59XQ1uCAnqN1V>4i1J~sBZ;9^kfGu$)O>YG<^^R`e2g`nX!|nBqh}~HGMce z$mGnd1gbX+L{S6`8W15Tii~7rWrOnb^8tFB%Spa``O?wxu*7r_q)({iyV9k=y@%S~ z5_<690qi(xMG)R5q69OrC0wHs_Y$xpBU7p31_&SpP$1t0*MZ~fu^>$-Mu|53=Co}Ykg_y4kXh{ zOA933i`$|%Zrp%YElf}NmoMw1Wlum6F|Z!~M4Ob`)>us~%GVbyu#ttG9a=c#o_C3e z(o$3L@bN!h4~mbs)YtztXYD04_4djQ*k7>efYICD*(oy{mRD7c{QP;honsPITYu)F zFzHtPGfwB-MX0EtbX9)&@*y>KzIqu05P4sips~j1Qd(5B^X?jk{+=LT45!rV&qXMG z|A_B)uMdY`Yf|nfR`WQlLpYj*QlO(_z4b{g9WCu`bT8;v!J?Rd!wJs~52Fn#O4&za zl@69bio(EANk%Zf?s-@tXMM1|yi6(ag%i3gQBls0jxqdhQS8N_(hwH@33KOrUE$?R zR+GL*!0*Bm?}+0&0i6vtDk?6z*Tu=?1*`x)9i8qh#cUuPd+K1v6C!uKuZSb)TcEYB zuA%}hP1r3J6%|p4z74n+Se)<=*RNmS+S-!8l0ddcOGlT||A~}OZ8d{9r)o41y;u7+ zH&UB>82-gATf3_9>i8*Eb7yB~W1|u5EZ`oO$A0!#Dts}U*u>+`xfqf@!pz5_zeO70b5iQyTz&H@yC)BZoh?)K^Khx@=usWkFBRNWsV-Wr( z=r}nn6x4WSs$x*rKZnmsSbse%WW7ato0a6U;CSKkm9o7E~jK^gF5PDOB#`nxlN|dBnlORfR5ydH9sSD~BJm=}s_vmzr5?V=!+$Icv=xRctarL|UK#vR zL}$o2-u;7H1phW8|2aqvb#GB3uH8rb=gecP>Nc34E*Y^I+gH}pt6iWHDf|vsECF3y=k`4+}fBERI+xSxY&)f4C^_I>Iek+ zZmx{L*~j{2y8X_2gAX}tp12grq;2?mGo*2Gp)zq*4+d=4NK4|in?_1pUXGUKH(AuH?lmfrb9b)qLOA0MBa8(-$=5+|LyHAmE~BYB`Z$~LaeNeD(x_eOV?Y0>$%SGn4Z4#f4J88`1-khC@!mneVFX+ zuPEny0AWh4Pos+Z=%v;#=AmXU3uzK%L4b4p#tky!=<~Ca&TlRU1%1ZJ$ueceQ@b;P zEYPrv52H*V2EI+9S8LL(tb5c&?PNbWoMHUMAbYUc^A0AFXZN7qFaavYX*&>x%rgke z99X{C_e2TT_E9%%e>@tDD{(Bhik}@ z#f6=dUH<0PvtNk?dO+pC+(R7=Rj91iMC#dzI~v;2-2R%6Bon^?E#sT!{MwGz)=Jw0 zi(M}a4n5uTo4M3a@)H1KvazjqC6>Twx@`VdwYJ_3qjmT67O8OzrdN|UQM@>{;bY&I zG+9m@%`XC>s{e-$^!0Utg>?6(m6!>w^{1_l z*v2x8wOS{Z<=JN18#r-@i}UyS8w6*^Zm0WeN8i4c2%Hsbt~y;;y@rnd5qLSvv7zvo zdp1CoX^ag6gVNX#mzk6Ez*D2#I{oWc$HUDU&mb0~TQ@Kl2U41AXUyw7H~jsb8813w z#9watpKC)AAI(9=3etn2g0R`jmU|_AacxbGEGB*Lpf`Yb=i%YWLACQ$BNN-~9QR@RjSf(8Ls$mafUowX-i3%qd;!6}=@?;jkg7 zU8Iu3ZmvN`-~Ow|AXAZ$n7G<0l$g-z5Vl$r+s?(2Xba!cUtA-Zz(gSKty|3Oyu8x8 ziOc^+2BwrgSOA_(I475Wd|b1-s)E<;c=>Qk0vJ(&Q$}HJ?arUc{W+?afR}pqK3L)Y zL;ZV5xXB4=S4_f%>y;n};L50Tk>_W8h^qLvZ~4h*hYI{CM0WLPG%D?Xj8}HI3CMIx zvvGw^U%reV8R--i=|n_)pi*hyN#;&xWWnnvKcA7hKO0OzFj8j8q*M2bubL{93M*Au zOgjGRjllUWPT-XaJFEjqNubS`xxJOef{{@0PZ`H{ad<9=?kRvrpl3aUfTN3zS&b;; zKhD4+2M82@o{^tl=CI)#%M(~Qyeb{5Mu3MWfe7~t3%iCS5^oE&wM#9DutzIuYSODo znkY_8Xzs6~jaME0E;Ja09S8Iw8s@zRN*DWUR=`YlbRc#c$Hv~Bom>EYt|0UwIQVTz z3FUaljA}I^mbL^hvr$YSt7-1MvNAF8sewU1(7wpoJ+NDX}RkyEyo%1GI{d4_Sr%+T>_~+p%k&^>_Jf zkX6{VeradW7?RV_s4(jE1=`-t&1Ganjl~5IAMxSi@d?m@tc{eOX2Z9tt!6GoF#yD^ zb&D2lO@~rspiSC@Rz6UUbxn_xd!(YWGBxG&q{>Hd^V5FF>E+Z^19=>$md{g~75)Ow zyYG^d*BZ&nC_FW48Tl?xGj&6n z%oKb>&(>9;@GvY=r6J4bEs_K3c`)49_O79_;R) zbb3||T=L4GsN&0)hK4nJduI>m%(Mg0KbQ;`M*4_mKliJnlG#sJ{}uPk_hN zpqAO+7j9h35yXk{-VPRlviMrFza_r z*DFv--=itPJ8FE?GBUE=7P;nrX4l+d@oF&dHt4;;FS1I6hs$EnvYA8oDNCY0h-At%FnKJWtaR6 z_wG_l%IB$ko81Sh4?a!Fe0=WuyUtF?ziKLuigZ7(o6Biwb;Ab6Ab9XRej+VN^~Vbw z%ldl3^na@4xNRLGakq~aD8M#!zN+e-cIDDI-tC>hKpB~44dexh{*0;yr>wTNCA5IQ z&<$&-Z=v*wsj42$w|B1$xaCSys_@_b7b0t)(K|$+&;L?a(BmUUBo6-HCcpr{qWmX3 zg-<|{jsFjW-hV~jU)Pxhh2PpCB@;yV|Alt@uUL~1lp6Aae+mNRH~(+~ z0#x?*x^n4X2{BK&77=<5lHcn>c|H7>M#JM6XhCLt?5p4Z8wyAH5906tklw@Wl6{4| zkjHf~ncho%Va7^d(}a=tijtdd;1E8;M|GIGI%shBWyT<51jF}e14hRXIY3Ie6;YgETO@cw}{2qa2GL`0<) zUUn!4Pd)?NuRK>*+DlM z$f4S^eRUGHa4@K->16U)myZF3qoB|WqGIlkPy0YAhflATGExJ}LG`evr5ZxIkstCi2Lg8=y!>QxYAO_tlFt2_kFKgj8dN=t#Qc>DG((5^{d&$ay; zzJf5#!vlrt_t&qC)YR0(#L+;MDk>}cjW4GiSk>zWVah+c!=P0)^aI*C4QYVAKrozI z31Y*^YL~&@-q7>AH*ZcYFPj(|vO8_he*B1@sVL6JAS4EYWnim7Km}%q*0IztlcS*S z4U6L^4!UhL@=KR4!5RbZ8XFtC(tb_%T_T$+_{xB^yqWvL zBnXHU$g6*OQsL^GdwZ#sWB^&WXPa4tgqnW&-UbCd8L#8_IqQSfVTux(%tlZV(}E%& z))RR8K%t5&G?1g3{Iv{-)+cOP+(dXNBdC8h68?mWj7*?XfvcmUroM6Y>Ko1&n3$+k z5&^{6_wT=b$PitFg9n?N5p1SP&z@Z!rvx&oaVCI}rJ<>5u`QCtGYGB&Br9kE8T(s? zHRXZP-2lDbtM49gy@lCXkVS)-xD_Y5;gz{LeFbto0Hp}LSUlX?`PoOZ`cR@2Ff@T~ z6bw4hk_Wv|l>7OS4?aI|x*u**M7RMc&dtpQk$81gRUE$?4e}xFVEu#Y7HL?Ok+B3? z6Fi%;X=Ol!r;0#9MdSmDHKKCx3nNW9Wc)F>A;G}~pn?X~I3LI&{-R^=Vf)}`XJwg; zSFpK_=cuwE9ctK6xP1Ib#QElxnOTm?Wojb_@#EuTu)cr>n>xCE9b8;;a=}2^lCcLo z&AA1u_jshf9=xm~IUf44^6z)S^bFk%;2p#1)eS8zYhkH>|Nb4QIQS`W!=f`?aX(XkzjdFksnM8tW90P_ATVWIO_z?nX##?Ar8nong_EJp~2DgKabc zL+#9`0AT+M(Q65OsgI8jEZdy8&uU?PGuF1YymrfXiu7P|!R>&zU)q-eogagGNGp3H zf3bBaY*E*s&GYHgCkhdNqz)B$#g2FZP-q|p)RpDsM<*wsJBNp@NlJRa!UDPv4mvtX zvx6`6>Of!8sTG43t6A5wd1Sn+i;pqd@K*3xfd>|-W4b3#2x#f- zM44=#v%eL@B_-uIA7TAB?l+Kskq&PAJ}~9HU{x+IW(C&JZ3ck_CJq7{8XB5)g&#Q^4cFF(A0;>c*0xygSq$w(kzD}eCK$_X zfK+#LbA$K>5yTMe4`pCJ>#cQn2Tq!Yr}9bdsr+uJYuh^5RG~H0+R>3F@DV>~OcR2i zdTCo%*BkILCo$c@!wVo{15j51MkQE9=U_{`a{2Nvbe8<*(T-qtI@(A^fUFhb!y;H` zApn9EPsnuQ-zni^Ie~5y%J(^Wc_7B~egFPFOdkj(z<2^yLWq-*dMSS~wgR7sIo3gC zAFdXbGlMi5i;}s49ejFag~xI86;asf#1|qwijDo`x?SZGrhC9qHmC?am%+V^;$g_A zQO#q}%x=+%o%d8YDqxT4YiXD5GZYl=XMbA+5!bk1JZh^(Y`7O8_y%l^>zXW?1GTak zXpJU*{S$k+nvA%15NE=D6$R@jU6)nA!5r|m2Bg*cugts6aso}eaN!dFN-m-ZqH@jg zub)xW0A-0q+v##)dol&YttmGyiiZgH60^rX;VGjU2u!P;zd1X!IH=dNf1J?#WO&dg z_6i!0C~&%h2fGe25hv#Q9Jf;j5VvN+SL%6?#T&4xe}OmdkM$u%%-8?XV)gWzg?Llp z;6S>f9uZaYMqc9%U}WOIAIJb;(BsRG|04)?jaCoZF6+ZZkau6m)<_kx^eeOX|Da|c zKfHt93)ThJ*AMnIUH@vOBXR9!K6sa*Z)n1eE{oY}^zI+;$P}-)<$@+}y>8f-F9Q~= zC5zDcf}*>vZ52#R6rQKx1i6O|6aX(Zb-MKE+}~i77^1+_^QybMJ43jsg~bFs{%vaM zuC;9*b_$C3^}2Hl3$$dN;F?mUQ-U6DT?phMNpbODJ5&fqkk>6nOLMoSgh_sENB%P@ zY~tw<4!bsT5DaJ0mNn#_tI*1Vmc`Q25-+KYP2w3XJNv-kU>WoTL0b+dRA7OF3l;T) zudm!UittuA6N8P7O|dW$QWA7;q(K@ET5EI+=&rUn9Y9in45L`62~*@Dl8oOo2d!#A zAF66OC-`&f zi~k*Bb-!w9ZAG3TK}3XvOJxv@zCgayV>UE=GT%E%~CT$1z}rT(5KQPPPZB%LmL?wbO-2ie1*2!K%OQK zG^O4OGD3y`***>LRpi1QPh1ZV1X+3yz!8{z9ykrb#AF#xK!BDPDCr-TcQ-bsL$kKH zco#hS0I49WSlQZQVq@op{Y?1i=l8mU#{Oat{9bV1AZaCGG4${AIN4hPv<%DS9FD{Q zV-j?&y*xc*xb2n!H-H<*>dhOF@wtI>B2yt9j_CNlumjY&p02Jp9JS!M`5U|tS{1fS z&=3dz*e*O>X(^Q_#NY+^3E;`C?QL%OEihF^yh2*9LqjPlyl|Ppja{HOwR03gkA0WX z<8e@UxU!~ZY*bWKP|)wWIW`#5+1XiYnYd!pL5ta7oVo#73``Yp z1jO@a&sf3L1?2pd>zIxg=Wf!{pK^1}?d|s`-7nhbtQR`tp${w*pYz7XCRq{C3dk=i zD=S@`oB(&Zlir2$z>Ix#U_j}7O3Yj?rwyE4VE=}ec|Pn%*cISqUXmY%2;z2nxCP}6 z8aceCrl#1O2Q)g!Jq%Ut%@RTJ|KrC$az$Tt<>`DoF#xD}QDx;p=;*!}7efVMX%dGv z8aY%+5H?IoJ8;{>(j_C4^7FNmJ(gAgW!@Chs&Wht3#-pj&{S0HhcW%D-wruCwA~QA znt+VN9^I)yoQ*U$`(`dD2l7Ag?CPAdD%Ys!26OhVstMBaC2VK62Y#~WEb~Y^QCJvL ztT1ZuNYYS2m*o*0Jb)Z%n~LCeL%6@&LZz0IS}$MDuC7X!!PZ}&taXRW=!P{pKNLYe z-BD`a{6=IK=ReUSFqPorOLMPG2Ymnl$|n$IQ;Lf}t7g$bY)ngEzTFxoB`Ik_#OT(p zTYw7Uzfa(#Il6EHfdV)alc8(>^C#dO5cjr1Vb-e|6B7eozKAU7#^1Vi>yF3?oB{xc zj@;vXBWODV>zdnPU9wl>g^6DabZ`AXe&hx#>-dH1$yymmSsq@b4z|rSwNalgP64z- z@pxp{I|tAxJvG&QvZfkv8yA-oV0`d(%5(H=`w={0c>EZS-WjfP+5w|eA6Bh*d0~#n zbBx3PhV|$K1SUaZ&^m{XqyVuWQc-;g4?j6Nf|zYNT$ox|$o%;65=39QA#U<#GBQYG zAw)yf$*$4IC9P(6!5>sUk zjxva}ln)=a0Sabg3;Q|stFaM^xuDiL=_uC6yu4Kq)>}MD?%ut7`?d|7Rs!UJ!|D(d zWGR4qjEu6y{(bmi8ZuA85!-RXj=oWvHD> z05uBUH%7&v=fDOGEO5H{6A}(9$%ni@Y~ah&YL*0re~6c4g@o#0kn|0zMvmDiX~vZQ z>PSCMRca6e`_={R^NA`aVWfxmT|q%Xlp;us;7Ey3+$2V2dn+r|3fsqLx&pF|V6y_# zzF4>o^6gzDEesDGz)-nIe6o}t4|y;b!T0%8V|ZeshM|T{)(E`uFM7$p*Z$iS-brb- zHPD8x9#j(p61;3(a1s^o`%B!Y|Asl9R+znhEsknsZM_VurTQ0gm7{cd7=wi1>zn)k zoBd-XZHbb={~zae{na1;Pk!+~IXxtKhx`yhx1K`V|4x|wr|J&qNFW`dc>Hr7SgeAI z${}cQr9>0ih@>3YqOM%ZFhYs}Y^6cw1gFxNRF6Q=j{P|WM?XQS-uJ?5Fkd^KQAY^Q zh48C~OiO@zHEF|sWgwtlw;FL)p)>+glqgQCBq+xd6LkPUDCelisj7-khKy(u5)#tk z(+UXGDydO>KBT0CVon{jF%c2Apqc_!0L1N{xcu>*x&=^*KEse0&j>nRfMog?ynErM ze8m5lmUack99;R_KT!IJ|3R*1EoC&dx99pX!ghhvbL#$(Q0WMB;9x*Ypi*Lj2bQrO zW-2OgrvnH^fn)+oMZUl&NJlcKe)|@I%M2k5QVNh7r{L-bZem|z{@!%-{xMZ-Ya zK@6s+4`@AeUmbVScxiAVuz7-c!rz9;lSBtT%Wszp?h2`XjQU9YQ7z3^%`h!(@2Dj#ECfwpuj!K4nZ1v?_a)rf&C4xOjkJ2M@J{o%j>zMWE)_r)wMN> z2M?Y-2v(ufDz{FB=@z_ym(8RPVv2Fp{{H?ze?Qpwq25e*eGDKJih2NkU^NH4aT^xS zf*Q<7VB-lKXL1AcE(AdmJiHPh5TQx{DdE|Iz=h5<93mxSZ2U;77fKq>An=!xk&!j0 zt3W7+u!=?wT0&N-GU9SzhQJgLo=Xt!T{Kh2f2h};9|CCPdU19DxUme-B)kQ@$Mw1e zpFT;8hu`)oFK8uJUoxYmhVb!hr+uzyPPCK`aRPea?*n058Y@ zA+vp+hybCNv9TivMeFK-TQiDxoB#bA?378#$?EFrkpX}jS*0#OO9(4$-ntxM`~+}9 zHjZ#~L||Yb6w{#Ua+q`*oPQ4jxT?JYb;vPrvH(;FA#KIvJYsN^0yzjhsn_PK;JlEK zQ0&@CD4j-zhTw_QS)OBb_1|%*GKT$G{3##+S=%kSSH=T`hSrl-EkaXU`vR&KRw)8} z{E^||weiaE*R61D3^~3Z)FqIea7cM0;^Njmi3ubwH1Bwhs>7YG?S2-9TL^N{0u|SjeRFId5Rtw{&NV1E10&54iqdf(Ue+AjPw9N}T{?{%+3_66vs z@+G0t9%Ix&MXyjhIXMZe8`}b2b!3@Pk^F%R1*M{?k&%CQH_ZLe)lLBzOV-LMn#l34 zy&n;A&}g&1%9+!rbuV3-XtVA}fBABcm_tY`+xnp~7`_OiaTgknfZy%M6j6)wOGa*S|&b}RA*yXt#LSJ$s~1#E(<{HB}Rz_)Ke5T8AN4ozSvEj5?6ymBDk6>eQAtYZHD z)#xd)W?AAoBbOoK$c^}N7dUoMpke7~sI5f+$hj2V_o(b1Rle^UC1F4v!|Lv{liST! zLHP`JWaL3%xug?!a=^+5SMiA=UI77$&6`DSzVBt|#=)T6Lxx=0dEBWn#wEmSkucz~ z!#n;1D;+9NMA{Vlfc2w-U#Mgnpp}l}6!1nCD`#UPp{0N) z8XY9!4g&Aqy*5y{Uo0vgVnZzPNSHz)BWKQ14o^!-01MLTwvhY*xuWs7RO**wRNa~q z1yiMihpip+HCm@89>-wGuB(x=x-kZMpT)XPA@OHqWII|9z`UX&BBv4?4CH?Ch#naJ9Sg%9#9 zPe~GP9v(e@bHP0+>YA#m4b9EQsINr_5zQB(cpX8JFiHmujWG)F@&@vzuXT;a8?<-t zXDs|w(v$tQ2H+0h7Y`%@DtQkWI8SCAwBm(bqcV4P)en!nlym;Wi+?}+!Go6O<^j1a ziE3O;lFaaCV_h}L*-u3Z8+jVhhQ7@T=-o5ThRy0RYg z7P9u5ngEDQvAqz?<#?;8Nj-{+ifV+}=%kLN%d$;7g*M^=DBIkx__qF(k!*ywdINO- zE(2`d0Sm}A;uaHIMA`fP{d=rZ@(&x50s^+8`1JAdLDPvQ@3@rIOO41f1cA&p7L$^a zGCxc|k}e|eSC982?>d4p0eM2cjCB{S@zYB=xy9jOVa%>5%XzL2qdFtBYp~$siNhz^ z*x2A1UHV=~fh!gh$`kdP2=-iaS_va*o+2Kvq;zsthh z9M=9?bQoy!A$-S=G)u9VQOfisO0P*#FLS6S;t~wpPvp`7^=&Z(^A;C;pq&7%AQpA1 zb^S8K0z)V zM7hxEx@ypLp%*|t*`mvubs--p2$zQtq=7ackGV8sHJ*B;KYcI}N4tR)r)BV2Vqy*w zBe4^sE%yX5gyX{dw2_?LX|i6e!=`KV<*4M>v)+^tCezT%@d60{%1061O zjMz#gB_wq9^gj5|sGL7PH`%B|fZ(ysV$r_zdT$9aHy zngA7vg&4sSWk^i2BR+q=Wny9ieI7Cm@cIxkjQVThDyd>XG(zW(fP!Wd0!u_VZ{7}4 z#>~`IE)~lW1@$OeW$E(}$ue zar9OW4)e$ieI))TvcNJkGr#!lHM>iGqn!{hi`CeFe)?-sM3H?Q=?~&6suMphG8rK{ z5+KUd`1o$nvCX=t_qN^W4S8^rc7(Xf@glX$GgFQ2_(8nL`1(l8Nk!=VLWL751zuFJ zad1HQ=NcTmW4sH@5#HB5G&D0PY6)R|^bX%$y}Nc$QK4B~1b~MyPj=!&8b<#o0QFG4 zV^b;%I?ru?qrstc>c=%-o4gNo7aSvP!zHuzBNB;EH!{4Z?+*=Nr6UNDm(Ua zYZz}(6DSbJ`XH7HH9sMqM>j?A;K3odQ$XLk-^DE`ZSQtcMWuz#{Bc6UetUa;30Xu6 zA;jpB+9I)nn$FC4{%!twLvjA`JNKuYU+pzrT&lFOC>2tpc%&$uO*;bhX*3~o4=uYk z>OFvSe6*!JyANG>gJS@yb6Qy$bgqVQpx z)*=3Kxq5+~vvsM{O{_8(XV~{=<4ilK?^(8J2Vt=@7{Q;XIriI7J z&;~2ifOuq3%c5FCpV2pJqsb`%KqetUj*SYq3gBNdda>4m0^~{@K?}pHkbEv36==oB zj{`_pT?F$=LWtZ2v4LWKq0$-l+$@Kg)cpK>bXnLd@fV7!;5Mrg8&U^h6Yu)4Vzx z6}#B&11mSO-&lx%1WbRxx&{OcIj@v^_CR{4qos8f!Hr5vN}8I3U0wIk0#Z?dnn8^R z24L;lwP32jAvpo=!=H}b+<>a^yGKT4X7>XxTxrq>ZRHrZ531%*3qX%mA@D*WO`3-R?G)Kz)u((1Fh3;N`VI*}F%vW$Dg?fPgQB1@Mnl%~{y@y<149|5$!$Vo&o(+7t9t$=-jmNSse@DK?qXwK zhwb~<9eTzu;%~ZD=r$GZ#O*p4hgFmy&(Fgy4Jr<_H-cID`1xyi%Ln(`Qfq)vK0pnf z#vuGl(C{P78r47Y39oK&z`~C$ru(>oK_BuP`)_aqO5eyJpNZRDWYT?2P&egMMTO!4 z>P(H-UD$3xN7bCCde<77CZZCg?GQ-*p;+;PctvI3wU-@!I{L|rv&ohDwH8WM8h?9J zSgE8-7GZVTNl)(#xD(V9_Hwkxww}R(fv>W%^et=Jjz`70%5;|lZ%?<4{UPF zg|d94$PsMqoSaEb^q`jAr=8aVeDDI&v z4F^h!P|4wvfS6K0e;$eOu=@Y1ie}*dT)$xhSVI7Kfw7dKdCIdM zX;M`rp`gfo@!}H>m4F~4dK8gVSl%{n+b26xI}?(AFHTJ-k%+6))uM6zHa^--7A^sf zcV*8Z0vsWc;MQdP=$>bUP{~d`@LG~sd(d-rKgiaJ2#(5rFlA}}rsPwSnDW%gwi}OM zb?M`oT;1ei*pKeE$QR1oQ!`aPVqJGFb~spwPfugnQCuauQ(c2!O{~U(s*3yXyGE5u z@5kdCRD;5N*9sN=wtlZSyg97a$Jb~!ek(h%Q>u;2(wOkq`HdT4BVOLca7a7o#jc2p z9o@`2;FO@k6O_I@3t`AOSM6NH`>?nO`UR)QV_)uQr|HyM?5*O)-(%0_qh0HA3q{iM zj%I7g`7|MahxOotk@N9vuISE_X(hmT4W|1M*;!?J0vglZ7ij zpiO7jG)F5$+zyxSF;XL)iac;ChtX8EKdMnREo`YhTg38C>HJ2~_&*Z*d|pXCS=r!- z4Ee&6Dn1@;wSLOn|KLfyc5Sro%;CK*`e5V{rB_Rzc**+9{;pybc99HAbNRA zwGwP%ea4-vUO=9y`=i~vDKW!F$giez=eh9%B5iw$<|sPSRU7MMYqu2e99_Q9gNJ^Y zrhdsOT~MtYoj-hDrga@W$=@DlAWC5wer*X#$s=klL#es@tmmDmGl^g%>{90MH(H0=H?vx08A8tL8{;t+lWVcq^ z4yLU{3WO5&*OjLQ1i`ZOBQKRLEPneQ>}=*}*f_m55$?ffk80?xKyH6D9r-*w#*v&W zK>)C->W40znk?2>NH-|cRXDyTS`HRD!mB|fI`jm9zMx&`V@?p+=LtiDP z$JJ^Oqqyw*`TDIEl!I!kyb0(uXV0BGp?n6>L%W#jWq#Ge1QE7$ zz`c9uv2*kC(9{M(aJLLR2L%S!b-Fr;i61%Agb2pX$JV;cE?3uk8U5Kn2VlDg$cLH= zJ+Ef!C4{odBh4V3z|??ntmqnjrD+OqMMXu$($W%CC)ivM35KXjKX30bRJtiCgnU#u zd3*c&jjmqJGi?*P|QRkayELrMU4!!B|3UudywHq_Gi_H8UAq_^D~x2|5ornVmLBgB8L zkN{aH2D6SVTUoyL26Wk;5 zXJ{x*ntx&EpYOkLF70gFwffvF-~VV1vfcb;sMV2FTeBNdmfoHombPB>2Z)&j3Y7Bd zl^~?gkSPrLuXV{r1Gg&O-*h%c2(1T{5^&!eqFMz0jWl@x0EoW7&Gq`i@HQTFEC5_) z9K;lAp~VAbw$)2&7uos5pNkZQ-i6L(k3s##v%CmxO?uX2Z~ugqE(ik6|GK^NKu!X1 z6i^w;*y!kJ7`Wk3GiJYV^=4w!ug=Y-Vq1M(FmnV4wjv>p%#NodFH3265n|0jU}ZvL zDHo{>TIpd1%n-P6Q=?rC?4B86!QJJ1`|>D!Xh zfrD=TL_}{+kdAOpofrKuUMhR%-Fl)o#s|*Jl4D1R{par0dS@Mx63t&bMDv;TAC7ih z)Q#>Ky|eA3BKe%v_6~!+F>DI&H+;|iJn(qAxBKNWIAD7=rjp0n(IDWP@VyYr;QBq= z-;8_wcEyNTLb8RzvV|Z&BO+2=EyKZ}y07-noPV7jp$J;Jha_+r6BdzT51mQ;VVeH< z6aO&s7-9P(UFW|kOY+jLf5USD&#^^isGd1F;iDDa5>{mIzHcOa=DTQ<``NM|Gu26~ zbImdv+rT1p`R7{M^w&Q4hVgZYnGX5+$?w4(QIkji)}E`T(bXUC_oX??w7lrKoQu)h zV0CPHHyya;OQY`od#`@sp}|my@+vBDA0#AE_Ab?;uR_P4cw9A~`yQMVTxi9fsk*_% zZrRX5q_mB8T^4on5ApYGSd^G0k)#f6*G)|nPR|-_SdbKc3hLJ-&-#s=rEKDjw z-2O(|*SO%`hPpb-#k>4}VIIFq$bo8%@)^n$pPL_NsdVh7cb?aO>#khs_=$ho!PI2dJ-TDmv|5%H5l>J)D%;mcea=RkV0*u>eaNgO&O_n z7@^XE24A?0AXzRb*mUsVy4i_Pggad)Ot0}eddRrIZL+u53E%-(wuuk))((!u+azjg zx$JG^xw%I}g)H^;x;n~8g1HRl=JJg$b~ffrO%JtDRP!FB3L2dO81O|@B>vMUpT)s6 z_p2@Bc6N&C8RX|OhtKyu2@JeZ=;=9bQJRc|AlE27aTp@-5F!r$z zt+#K#zsW1<}iOiYEu6i=;( zgsl2}uhquugC;+T0UCM8pqU!|VD_!Q-_Gub_Di+{44rIgOK&~ytzXOOXTV^)*f~Aw zvu2$pzF$F2&EUsiN?F$F(-qk{!d%)SfQB1OOC8^P|0!y}*st1G>3oy&u&z7tIA{UP zO4Cpeq27MKrR6+;GF~=ADaVDbr%%saxuR`rs~_&NgDX-7)!7?b?zc=_$sxreUIkLq zcSvOM(W#@5jnFTImF!Wj*Xo- zetdVy3-}M+?-ElOhwNA~wP52+x}bgjT-Y`V!9C;*@5;*Dn~FP?bW_w%ow8n9+~(_> zY-c0y=lGBC80 zlQ%5PW&v^FzIjGf%=qed9tsu}DrpidFdu5u1jfHv<2#Iuf|(6+o%7AVDt+m`WWP9n zwxr0-?ezKcPM+2K_CuJix>Jhh+tSi8(r&!zrF5NW$mxxh%EhbYOzt@H zefC5L+;RvVDJ?+#FV_V1_3yh=3JY_nsC4DotAvM15tBbCt9X5qB+F(La_ivUp6!4Y zNdM4eVp@lE>8dK1v9XWO&u=?_F0ws;=@4^w#HUZ|Y6?$?FCDTqzjo|cKb-cFVpkR9 zjs%C=-fRj9R@<{97&!yHsKSB{5;HPN2>U>{P*9~5RoPd=>-bd(bJ2Wd>td%VZLjFE zkw>=?E^5d^=|}d^@CKp-Z+|O_J$CJ+p_!f~&o*ZXw_O;@ypmxi{;rQWK29r)TMsj} zddnJn%QkK%bAY|sdSTXG?*o5$*sgIKYJM%{;iQ+BIMmEhL<{@mYps-qv4%~LLvhVt ze%fN(%`2pBWsjAruI_e1LQ5U}>8ZV2(d0QAa=jH# zq(Z2G+1sHOkM-;0!pPm92Lx37L2O_8<0I(7-*@>sNj$q#!>1abHO2HFRZqy$c&Ycn z|KNEsCA&XXw_y`~vK93e8dl?Nl!uu2UidTq;RQl0>a%B#PknxN6KyNt`Tr12C*b%* zL`#zG*`-yMFb?Dv|1Y|gpS8&((0={ zAZO688}$1F@cX6wwK@DodHio+41zb8a75j@tG-+!%il>8iQTYz{7*QL`kh8<$9|ph zuZOQq+uYn78gImh0VXuRaib7lZ{NaYAS80PvJdt&j0AhaP@Ht@SAUCxX_jH$itzz5 z$B#pc(*R=~7(mSM0*;Kqdd38g>PMu%+ltUZB0@o7hH;;WecrUmettSdi49Z=hI^o+ zMbm?j$|iK2kbMA&XncPDv47?IH`Li-sO5=>sAiz~0he&OGV);FXx>!&m9%grHZlr| zQzuVCTe6kZ1Bq+kWT6!fpatRcPWL)AWSHfFQ94M+!uTXYtbe}ql0b(8SdYko=|e;Z z+?Q8fu9vXJV8R5NZyNzjbA#4z!}|5mp&;TF8GXUQ!I@1+yqs~rixHZKlJ|QLv}7iO zc)@}1SWi*4`NiPyFEYz}xO^EG2W6|2tq0!w`i&brejo0cRM*xLemlC}BYP^MkKF|D zuixkm5x~Fom+X?ikPW|*BTrT%6fecFXJ=k4yan+DA_!&ex^S^D9rRI4D+nCSc!Uaw zddH5(@$s+?KNYP>@>sn~%a3IhKXf?I#X`)JAhi3rmFH^|u%}^7)M`s4!f2sOoFx z%5dp0uLUduTKmefGR+JV4O7#k__f2NPj1-KwY{pfNj_6RPLPzKbN&g)dGPQ2pEZ@O zoGwDakH1)`R)m>>X06zgJQcF*StXBI+ByQ-vhw=5WEf`8evDeudf9n=E+y3LS?*Hd z`s-=3wcM8UfGn3^x3NFNrNIneTk_mAD>6=}ax;QSyU*U

8KT65mlp`)D#;)%iTz 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 ServletContainer: request access token -note right of Client -(claim as Apache env/HTTP headers) -end note -ServletContainer -> ClaimAuthFilter: Servlet attributes/headers -loop foreach ClaimAuth - ClaimAuthFilter -> ClaimAuth: transform(Map claim) - ClaimAuth -> ClaimAuth: transformClaim -end -ClaimAuth -> ClaimAuthFilter: Claim -note left of ClaimAuth -(user/domain/roles) -end note -ClaimAuthFilter --> TokenEndpoint: Claim -TokenEndpoint -> TokenEndpoint: createToken -TokenEndpoint -> Client: access token \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/mapping.rst b/odl-aaa-moon/aaa-authn-api/src/main/docs/mapping.rst deleted file mode 100644 index 33635502..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/mapping.rst +++ /dev/null @@ -1,1609 +0,0 @@ -Operation Model -=============== - -The assertions from an IdP are stored in an associative array. A -sequence of rules are applied, the first rule which returns success is -considered a match. During the execution of each rule values from the -assertion can be tested and transformed with the results selectively -stored in variables local to the rule. If the rule succeeds an -associative array of mapped values is returned. The mapped values are -taken from the local variables set during the rule execution. The -definition of the rules and mapped results are expressed in JSON -notation. - -A rule is somewhat akin to a function in a programming language. It -starts execution with a set of predefined local variables. It executes -statements which are grouped together in blocks. Execution continues -until an `exit`_ statement returning a success/fail result is -executed or until the last statement is reached which implies -success. The remaining statements in a block may be skipped via a -`continue`_ statement which tests a condition, this is equivalent to -an "if" control flow of logic in a programming language. - -Rule execution continues until a rule returns success. Each rule has a -`mapping`_ associative array bound to it which is a template for the -transformed result. Upon success the `mapping`_ template for the -rule is loaded and the local variables from the successful rule are -used to populate the values in the `mapping`_ template yielding the -final mapped result. - -If no rules returns success authentication fails. - - -Pseudo Code Illustrating Operational Model ------------------------------------------- - -:: - - mapped = null - foreach rule in rules { - result = null - initialize rule.variables with pre-defined values - - foreach block in rule.statement_blocks { - for statement in block.statements { - if statement.verb is exit { - result = exit.status - break - } - elif statement.verb is continue { - break - } - } - if result { - break - } - if result == null { - result = success - } - if result == success { - mapped = rule.mapping(rule.variables) - } - return mapped - - - -Structure Of Rule Definitions -============================= - -Rules are loaded by the rule processor via a JSON document called a -rule definition. A definition has an *optional* set of mapping -templates and a list of rules. Each rule has specifies a mapping -template and has a list of statement blocks. Each statement block has -a list of statements. - -In pseudo-JSON (JSON does not have comments, the ... ellipsis is a -place holder): - -:: - - { - "mappings": { - "template1": "{...}", - "template2": "{...}" - }, - "rules": [ - { # Rule 0. A rule has a mapping or a mapping name - # and a list of statement blocks - - "mapping": {...}, - # -OR- - "mapping_name": "template1", - - "statement_blocks": [ - [ # Block 0 - [statement 0] - [statement 1] - ], - [ # Block 1 - [statement 0] - [statement 1] - ], - - ] - }, - { # Rule 1 ... - } - ] - - } - -Mapping -------- - -A mapping template is used to produce the final associative array of -name/value pairs. The template is a JSON Object. The value in a -name/value pair can be a constant or a variable. If the template value -is a variable the value of the variable is retrieved from the set of -local variables bound to the rule thereby replacing it in the final -result. - -For example given this mapping template and rule variables in JSON: - -template: - -:: - - { - "organization": "BigCorp.com", - "user: "$subject", - "roles": "$roles" - } - -local variables: - -:: - - { - "subject": "Sally", - "roles": ["user", "admin"] - } - -The final mapped results would be: - -:: - - { - "organization": "BigCorp.com", - "user: "Sally", - "roles": ["user", "admin"] - } - - -Each rule must bind a mapping template to the rule. The mapping -template may either be defined directly in the rule via the -``mapping`` key or referenced by name via the ``mapping_name`` key. - -If the ``mapping_name`` is specified the mapping is looked up in a -table of mapping templates bound to the Rule Processor. Using the name -of a mapping template is useful when many rules generate the exact -same template values. - -If both ``mapping`` and ``mapping_name`` are defined the locally bound -``mapping`` takes precedence. - -Syntax ------- - -The logic for a rule consists of a sequence of statements grouped in -blocks. A statement is similar to a function call in a programming -language. - -A statement is a list of values the first of which is a verb which -defines the operation the statement will perform. Think of the -`verbs`_ as function names or operators. Following the verb are -parameters which may be constants or variables. If the statement -assigns a value to a variable left hand side of the assignment (lhs) -is always the first parameter following the verb in the list of -statement values. - -For example this statement in JSON: - -:: - - ["split", "$groups", "$assertion[Groups]", ":"] - -will assign an array to the variable ``$groups``. It looks up the -string named ``Groups`` in the assertion which is a colon (:) -separated list of group names splitting that string on the colon -character. - -Statements **must** be grouped together in blocks. Therefore a rule is -a sequence of blocks and block is a sequence of statements. The -purpose of blocks is allow for crude flow of control logic. For -example this JSON rule has 4 blocks. - -:: - - [ - [ - ["set", $user, ""], - ["set", $roles, []] - ], - [ - ["in", "UserName", "$assertion"], - ["continue", "if_not_success"], - ["set", "$user", "$assertion[UserName"], - ], - [ - ["in", "subject", "$assertion"], - ["continue", "if_not_success"], - ["set", "$user", "$assertion[subject]"], - ], - [ - ["length", "$temp", "$user"], - ["compare", "$temp", ">", 0], - ["exit", "rule_fails", "if_not_success"] - ["append" "$roles", "unprivileged"] - ] - ] - -The rule will succeed if either ``UserName`` or ``subject`` is defined -in the assertion and if so the local variable ``$user`` will be set to -the value found in the assertion and the "unprivileged" role will be -appended to the roles array. - -The first block performs initialization. The second block tests to see -if the assertion has the key ``UserName`` if not execution continues -at the next block otherwise the value of UserName in the assertion is -copied into the variable ``$user``. The third block performs a similar -operation looking for a ``subject`` in the assertion. The fourth block -checks to see if the ``$user`` variable is empty, if it is empty the -rule fails because it didn't find either a ``UserName`` nor a -``subject`` in the assertion. If ``$user`` is not empty the -"unprivileged" role is appended and the rule succeeds. - -Data Types ----------- - -There are 7 supported types which equate to the types available in -JSON. At the time of this writing there are 2 implementations of this -Mapping specification, one in Python and one in Java. This table -illustrates how each data type is represented. The first two columns -are definitions from an abstract specification. The JSON column -enumerates the data type JSON supports. The Mapping column lists the -7 enumeration names used by the Mapping implemenation in each -language. The following columns list the concrete data type used in -that language. - -+-----------+------------+--------------------+---------------------+ -| JSON | Mapping | Python | Java | -+===========+============+====================+=====================+ -| object | MAP | dict | Map | -+-----------+------------+--------------------+---------------------+ -| array | ARRAY | list | List | -+-----------+------------+--------------------+---------------------+ -| string | STRING | unicode (Python 2) | String | -| | +--------------------+ | -| | | str (Python 3) | | -+-----------+------------+--------------------+---------------------+ -| | INTEGER | int | Long | -| number +------------+--------------------+---------------------+ -| | REAL | float | Double | -+-----------+------------+--------------------+---------------------+ -| true | | | | -+-----------+ BOOLEAN | bool | Boolean | -| false | | | | -+-----------+------------+--------------------+---------------------+ -| null | NULL | None | null | -+-----------+------------+--------------------+---------------------+ - - -Rule Debugging and Documentation --------------------------------- - -If the rule processor reports an error or if you're debugging your -rules by enabling DEBUG log tracing then you must be able to correlate -the reported statement to where it appears in your rule JSON source. A -message will always identify a statement by the rule number, block -number within that rule and the statement number within that -block. However once your rules become moderately complex it will -become increasingly difficult to identify a statement by counting -rules, blocks and statements. - -A better approach is to tag rules and blocks with a name or other -identifying string. You can set the `Reserved Variables`_ -``rule_name`` and ``block_name`` to a string of your choice. These -strings will be reported in all messages along with the rule, block -and statement numbers. - -JSON does not permit comments, as such you cannot include explanatory -comments next to your rules, blocks and statements in the JSON -source. The ``rule_name`` and ``block_name`` can serve a similar -purpose. By putting assignments to these variables as the first -statement in a block you'll both document your rules and be able to -identify specific statements in log messages. - -During rule execution the ``rule_name`` and ``block_name`` are -initialized to the empty string at the beginning of each rule and -block respectively. - -The above example is augmented to include this information. The rule -name is set in the first statement in the first block. - -:: - - [ - [ - ["set", "$rule_name", "Must have UserName or subject"], - ["set", "block_name", "Initialization"], - ["set", $user, ""], - ["set", $roles, []] - ], - [ - ["set", "block_name", "Test for UserName, set $user"], - ["in", "UserName", "$assertion"], - ["continue", "if_not_success"], - ["set", "$user", "$assertion[UserName"], - ], - [ - ["set", "block_name", "Test for subject, set $user"], - ["in", "subject", "$assertion"], - ["continue", "if_not_success"], - ["set", "$user", "$assertion[subject]"], - ], - [ - ["set", "block_name", "If not $user fail, else append unprivileged to roles"], - ["length", "$temp", "$user"], - ["compare", "$temp", ">", 0], - ["exit", "rule_fails", "if_not_success"] - ["append" "$roles", "unprivileged"] - ] - ] - - - - -Variables ---------- - - -Variables always begin with a dollar sign ($) and are followed by an -identifier which is any alpha character followed by zero or more -alphanumeric or underscore characters. The variable may optionally be -delimited with braces ({}) to separate the variable from surrounding -text. Three types of variables are supported: - -* scalar -* array (indexed by zero based integer) -* associative array (indexed by string) - -Both arrays and associative arrays use square brackets ([]) to specify -a member of the array. Examples of variable usage: - -:: - - $name - ${name} - $groups[0] - ${groups[0]} - $properties[key] - ${properties[key]} - -An array or an associative array may be referenced by it's base name -(omitting the indexing brackets). For example the associative array -array named "properties" is referenced using it's base name -``$properties`` but if you want to access a member of the "properties" -associative array named "duration" you would do this ``$properties[duration]`` - -This is not a general purpose language with full expression -syntax. Only one level of variable lookup is supported. Therefore -compound references like this - -:: - - $properties[$groups[2]] - -will not work. - - -Escaping -^^^^^^^^ - -If you need to include a dollar sign in a string (where it is -immediately followed by either an identifier or a brace and identifier) -and do not want to have it be interpreted as representing a variable -you must escape the dollar sign with a backslash, for example -"$amount" is interpreted as the variable ``amount`` but "\\$amount" -is interpreted as the string "$amount" . - - -Reserved Variables ------------------- - -A rule has the following reserved variables: - -assertion - The current assertion values from the federated IdP. It is a - dictionary of key/value pairs. - -regexp_array - The regular expression groups from the last successful regexp match - indexed by number. Group 0 is the entire match. Groups 1..n are - the corresponding parenthesized group counting from the left. For - example regexp_array[1] is the first group. - -regexp_map - The regular expression groups from the last successful regexp match - indexed by group name. - -rule_number - The zero based index of the currently executing rule. - -rule_name - The name of the currently executing rule. If the rule name has not - been set it will be the empty string. - -block_number - The zero based index of the currently executing block within the - currently executing rule. - -block_name - The name of the currently executing block. If the block name has not - been set it will be the empty string. - - -statement_number - The zero based index of the currently executing statement within the - currently executing block. - - -Examples -======== - -Split a fully qualified username into user and realm components ---------------------------------------------------------------- - -It's common for some IdP's to return a fully qualified username -(e.g. principal or subject). The fully qualified username is the -concatenation of the user name, separator and realm name. A common -separator is the @ character. In this example lets say the fully -qualified username is ``bob@example.com`` and you want to return the -user and realm as independent values in your mapped result. The -username appears in the assertion as the value ``Principal``. - -Our strategy will be to use a regular expression identify the user and -realm components and then assign them to local variables which will -then populate the mapped result. - -The mapping in JSON is: - -:: - - { - "user": "$username", - "realm": "$domain" - } - -The assertion in JSON is: - -:: - - { - "Principal": "bob@example.com" - } - -Our rule is: - -:: - - [ - [ - ["in", "Principal", "assertion"], - ["exit", "rule_fails", "if_not_success"], - ["regexp", "$assertion[Principal]", (?P\\w+)@(?P.+)"], - ["set", "$username", "$regexp_map[username]"], - ["set", "$domain", "$regexp_map[domain]"], - ["exit, "rule_succeeds", "always"] - ] - ] - -Rule explanation: - -Block 0: - -0. Test if the assertion contains a Principal value. -1. Abort the rule if the assertion does not contain a Principal - value. -2. Apply a regular expression the the Principal value. Use named - groupings for the username and domain components for clarity. -3. Assign the regexp group username to the $username local variable. -4. Assign the regexp group domain to the $domain local variable. -5. Exit the rule, apply the mapping, return the mapped values. Note, an - explicit `exit`_ is not required if there are no further statements - in the rule, as is the case here. - -The mapped result in JSON is: - -:: - - { - "user": "bob", - "realm": "example.com" - } - -Build a set of roles based on group membership ----------------------------------------------- - -Often one wants to grant roles to a user based on their membership in -certain groups. In this example let's say the assertion contains a -``Groups`` value which is a colon separated list of group names. Our -strategy is to split the ``Groups`` assertion value into an array of -group names. Then we'll test if a specific group is in the groups -array, if it is we'll add a role. Finally if no roles have been mapped -we fail. Users in the group "student" will get the role "unprivileged" -and users in the group "helpdesk" will get the role "admin". - -The mapping in JSON is: - -:: - - { - "roles": "$roles", - } - -The assertion in JSON is: - -:: - - { - "Groups": "student:helpdesk" - } - -Our rule is: - -:: - - [ - [ - ["in", "Groups", "assertion"], - ["exit", "rule_fails", "if_not_success"], - ["set", "$roles", []], - ["split", "$groups", "$assertion[Groups]", ":"], - ], - [ - ["in", "student", "$groups"], - ["continue", "if_not_success"], - ["append", "$roles", "unprivileged"] - ], - [ - ["in", "helpdesk", "$groups"], - ["continue", "if_not_success"], - ["append", "$roles", "admin"] - ], - [ - ["unique", "$roles", "$roles"], - ["length", "$temp", "roles"], - ["compare", $temp", ">", 0], - ["exit", "rule_fails", "if_not_success"] - ] - - ] - -Rule explanation: - -Block 0 - -0. Test if the assertion contains a Groups value. -1. Abort the rule if the assertion does not contain a Groups - value. -2. Initialize the $roles variable to an empty array. -3. Split the colon separated list of group names into an array of - individual group names - -Block 1 - -0. Test if "student" is in the $groups array -1. Exit the block if it's not. -2. Append "unprivileged" to the $roles array - -Block 2 - -0. Test if "helpdesk" is in the $groups array -1. Exit the block if it's not. -2. Append "admin" to the $roles array - -Block 3 - -0. Strip any duplicate roles that might have been appended to the - $roles array to assure each role is unique. -1. Count how many members are in the $roles array, assign the - length to the $temp variable. -2. Test to see if the $roles array had any members. -3. Fail if no roles had been assigned. - -The mapped result in JSON is: - -:: - - { - "roles": ["unprivileged", "admin"] - } - -However, suppose whatever is receiving your mapped results is not -expecting an array of roles. Instead it expects a comma separated list -in a string. To accomplish this add the following statement as the -last one in the final block: - -:: - - ["join", "$roles", "$roles", ","] - -Then the mapped result will be: - -:: - - { - "roles": "unprivileged,admin"] - } - - - - -White list certain users and grant them specific roles ------------------------------------------------------- - -Suppose you have certain users you always want to unconditionally -accept and authorize with specific roles. For example if the user is -"head_of_IT" then assign her the "user" and "admin" roles. Otherwise -keep processing. The list of white listed users is hard-coded into the -rule. - -The mapping in JSON is: - -:: - - { - "user": $user, - "roles": "$roles", - } - -The assertion in JSON is: - -:: - - { - "UserName": "head_of_IT" - } - -Our rule in JSON is: - -:: - - [ - [ - ["in", "UserName", "assertion"], - ["exit", "rule_fails", "if_not_success"], - ["in", "$assertion[UserName]", ["head_of_IT", "head_of_Engineering"]], - ["continue", "if_not_success"], - ["set", "$user", "$assertion[UserName"] - ["set", "$roles", ["user", "admin"]], - ["exit", "rule_succeeds", "always"] - ], - [ - ... - ] - ] - -Rule explanation: - -Block 0 - -0. Test if the assertion contains a UserName value. -1. Abort the rule if the assertion does not contain a UserName - value. -2. Test if the user is in the hardcoded list of white listed users. -3. If the user isn't in the white listed array then exit the block and - continue execution at the next block. -4. Set the $user local variable to $assertion[UserName] -5. Set the $roles local variable to the hardcoded array containing - "user" and "admin" -6. We're done, unconditionally exit and return the mapped result. - -Block 1 - -0. Further processing - -The mapped result in JSON is: - -:: - - { - "user": "head_of_IT", - "roles": ["users", "admin"] - } - - -Black list certain users ------------------------- - -Suppose you have certain users you always want to unconditionally -deny access to by placing them in a black list. In this example the -user "BlackHat" will try to gain access. The black list includes the -users "BlackHat" and "Spook". - -The mapping in JSON is: - -:: - - { - "user": $user, - "roles": "$roles", - } - -The assertion in JSON is: - -:: - - { - "UserName": "BlackHat" - } - -Our rule in JSON is: - -:: - - [ - [ - ["in", "UserName", "assertion"], - ["exit", "rule_fails", "if_not_success"], - ["in", "$assertion[UserName]", ["BlackHat", "Spook"]], - ["exit", "rule_fails", "if_success"] - ], - [ - ... - ] - ] - -Rule explanation: - -Block 0 - -0. Test if the assertion contains a UserName value. -1. Abort the rule if the assertion does not contain a UserName - value. -2. Test if the user is in the hard-coded list of black listed users. -3. If the test succeeds then immediately abort and return failure. - -Block 1 - -0. Further processing - -The mapped result in JSON is: - -:: - - Null - -Format Strings and/or Concatenate Strings ------------------------------------------ - -You can replace variables in a format string using the `interpolate`_ -verb. String concatenation is trivially placing two variables adjacent -to one another in a format string. Suppose you want to form an email -address from the username and domain in an assertion. - -The mapping in JSON is: - -:: - - { - "email": $email, - } - -The assertion in JSON is: - -:: - - { - "UserName": "Bob", - "Domain": "example.com" - } - -Our rule in JSON is: - -:: - - [ - [ - ["interpolate", "$email", "$assertion[UserName]@$assertion[Domain]"], - ] - ] - -Rule explanation: - -Block 0 - -0. Replace the variable $assertion[UserName] with it's value and - replace the variable $assertion[Domain] with it's value. - -The mapped result in JSON is: - -:: - - { - "email": "Bob@example.com", - } - - -Note, sometimes it's necessary to utilize braces to separate variables -from surrounding text by using the brace notation. This can also make -the format string more readable. Using braces to delimit variables the -above would be: - -:: - - [ - [ - ["interpolate", "$email", "${assertion[UserName]}@${assertion[Domain]}"], - ] - ] - - - -Make associative array lookups case insensitive ------------------------------------------------ - -Many systems treat field names as case insensitive. By default -associative array indexing is case sensitive. The solution is to lower -case all the keys in an associative array and then only use lower case -indices. Suppose you want the assertion associative array to be case -insensitive. - -The mapping in JSON is: - -:: - - { - "user": $user, - } - -The assertion in JSON is: - -:: - - { - "UserName": "Bob" - } - -Our rule in JSON is: - -:: - - [ - [ - ["lower", "$assertion", "$assertion"], - ["in", "username", "assertion"], - ["exit", "rule_fails", "if_not_success"], - ["set", "$user", "$assertion[username"] - ] - ] - -Rule explanation: - -Block 0 - -0. Lower case all the keys in the assertion associative array. -1. Test if the assertion contains a username value. -2. Abort the rule if the assertion does not contain a username - value. -3. Assign the username value in the assertion to $user - -The mapped result in JSON is: - -:: - - { - "user": "Bob", - } - - -Verbs -===== - -The following verbs are supported: - -* `set`_ -* `length`_ -* `interpolate`_ -* `append`_ -* `unique`_ -* `regexp`_ -* `regexp_replace`_ -* `split`_ -* `join`_ -* `lower`_ -* `upper`_ -* `compare`_ -* `in`_ -* `not_in`_ -* `exit`_ -* `continue`_ - -Some verbs have a side effects. A verb may set a boolean success/fail -result which may then be tested with a subsequent verb. For example -the ``fail`` verb can be used to indicate the rule fails if a prior -result is either ``success`` or ``not_success``. The ``regexp`` verb -which performs a regular expression search on a string stores the -regular expression sub-matches as a side effect in the variables -``$regexp_array`` and ``$regexp_map``. - - -Verb Definitions -================ - -set ---- - -``set $variable value`` - -$variable - The variable being assigned (i.e. lhs) - -value - The value to assign to the variable (i.e. rhs). The value may be - another variable or a constant. - -**set** assigns a value to a variable, in other words it's an -assignment statement. - -Examples: -^^^^^^^^^ - -Initialize a variable to an empty array. - -:: - - ["set", "$groups", []] - -Initialize a variable to an empty associative array. - -:: - - ["set", "$groups", {}] - -Assign a string. - -:: - - ["set", "$version", "1.2.3"] - -Copy the ``UserName`` value from the assertion to a temporary variable. - -:: - - ["set", "$temp", "$assertion[UserName]"], - - -Get the 2nd item in an array (array indexing is zero based) - -:: - - ["set", "$group", "$groups[1]"] - - -Set the associative array entry "IdP" to "kdc.example.com". - -:: - - ["set", "$metadata[IdP]", "kdc.example.com""] - --------------------------------------------------------------------------------- - -length ------- - -``length $variable value`` - -$variable - The variable which receives the length value - -value - The value whose length is to be determined. May be one of array, - associative array, or string. - -**length** computes the number of items in the value. How this is done -depends upon the type of value: - -array - The length is the number of items in the array. - -associative array - The length is the number of key/value pairs in the associative - array. - -string - The length is the number of *characters* (not octets) in the - string. - -Examples: -^^^^^^^^^ - -Count how many items are in the ``$groups`` array and assign that -value to the ``$groups_length`` variable. - -:: - - ["length", "$groups_length", "$groups"] - -Count how many key/value pairs are in the ``$assertion`` associative -array and assign that value to the ``$num_assertion_values`` variable. - -:: - - ["length", "$num_assertion_values", "$assertion"] - -Count how many characters are in the assertion's UserName and assign -the value to ``$username_length``. - -:: - - ["length", "$user_name_length", "$assertion[UserName]"] - - --------------------------------------------------------------------------------- - -interpolate ------------ - -``interpolate $variable string`` - -$variable - This variable is assigned the result of the interpolation. - -string - A string containing references to variables which will be replaced - in the string. - -**interpolate** replaces each occurrence of a variable in a string with -it's value. The result is assigned to $variable. - -Examples: -^^^^^^^^^ - -Form an email address given the username and domain. If the username -is "jane" and the domain is "example.com" then $email will be -"jane@example.com" - -:: - - ["interpolate", "$email", "${username}@${domain}"] - - --------------------------------------------------------------------------------- - - -append ------- - -``append $variable value`` - -$variable - This variable **must** be an array. It is modified in place by - appending ``value`` to the end of the array. - -value - The value to append to the end of the array. - -**append** adds a value to end of an array. - -Examples: -^^^^^^^^^ - -Append the role "qa_test" to the roles list. - -:: - - ["append", "$roles", "qa_test"] - - --------------------------------------------------------------------------------- - - -unique ------- - -``unique $variable value`` - -$variable - This variable is assigned the unique values in the ``value`` - array. - -value - An array of values. **must** be an array. - -**unique** builds an array of unique values in ``value`` by stripping -out duplicates and assigns the array of unique values to -``$variable``. The order of items in the ``value`` array are -preserved. - -Examples: -^^^^^^^^^ - -$one_of_a_kind will be assigned ["a", "b"] - -:: - - ["unique", "$one_of_a_kind", ["a", "b", "a"]] - - --------------------------------------------------------------------------------- - -regexp ------- - -``regexp string pattern`` - -string - The string the regular expression pattern is applied to. - -pattern - The regular expression pattern. - -**regexp** performs a regular expression match against ``string``. The -regular expression pattern syntax is defined by the regular expression -implementation of the language this API is written in. - -Pattern groups are a convenient way to select sub-matches. Pattern -groups may accessed by either group number or group name. After a -successful regular expression match the groups are stored in the -special variables ``$regexp_array`` and -``$regexp_map``. - -``$regexp_array`` is used to access the groups by -numerical index. Groups are numbered by counting the left parenthesis -group delimiter starting at 1. Group 0 is the entire -match. ``$regexp_array`` is valid irregardless of whether you used -named groups or not. - -``$regexp_map`` is used to access the groups by -name. ``$regexp_map`` is only valid if you used named groups in the -pattern. - -Examples: -^^^^^^^^^ - -Many user names are of the form "user@domain", to split the username -from the domain and to be able to work with those values independently -use a regular expression and then assign the results to a variable. In -this example there are two regular expression groups, the first group -is the username and the second group is the domain. In the first -example we use named groups and then access the match information in -the special variable ``$regexp_map`` via the name of the group. - -:: - - ["regexp", "$assertion[UserName]", "(?P\\w+)@(?P.+)"], - ["continue", "if_not_success"], - ["set", "$username", "$regexp_map[username]"], - ["set", "$domain", "$regexp_map[domain]"], - - -This is exactly equivalent but uses numbered groups instead of named -groups. In this instance the group matches are stored in the special -variable ``$regexp_array`` and accessed by numerical index. - -:: - - ["regexp", "$assertion[UserName]", "(\\w+)@(.+)"], - ["continue", "if_not_success"], - ["set", "$username", "$regexp_array[1]"], - ["set", "$domain", "$regexp_array[2]"], - - - --------------------------------------------------------------------------------- - -regexp_replace --------------- - -``regexp_replace $variable string pattern replacement`` - -$variable - The variable which receives result of the replacement. - -string - The string to perform the replacement on. - -pattern - The regular expression pattern. - -replacement - The replacement specification. - -**regexp_replace** replaces each occurrence of ``pattern`` in -``$string`` with ``replacement``. See `regexp`_ for details of using -regular expressions. - -Examples: -^^^^^^^^^ - -Convert hyphens in a name to underscores. - -:: - - ["regexp_replace", "$name", "$name", "-", "_"] - - --------------------------------------------------------------------------------- - -split ------ - -``split $variable string pattern`` - -$variable - This variable is assigned an array containing the split items. - -string - The string to split into separate items. - -pattern - The regular expression pattern used to split the string. - -**split** splits ``string`` into separate pieces and assigns the -result to ``$variable`` as an array of pieces. The split occurs -wherever the regular expression ``pattern`` occurs in ``string``. See -`regexp`_ for details of using regular expressions. - -Examples: -^^^^^^^^^ - -Split a list of groups separated by a colon (:) into an array of -individual group names. If $assertion[Groups] contained the string -"user:admin" then $group_list will set to ["user", "admin"]. - -:: - - ["split", "$group_list", "$assertion[Groups]", ":"] - - - --------------------------------------------------------------------------------- - -join ----- - -``join $variable array join_string`` - -$variable - This variable is assigned the string result of the join operation. - -array - An array of string items to be joined together with - ``$join_string``. - -join_string - The string inserted between each element in ``array``. - -**join** accepts an array of strings and produces a single string -where each element in the array is separated by ``join_string``. - -Examples: -^^^^^^^^^ - -Convert a list of group names into a single string where each group -name is separated by a colon (:). If the array ``$group_list`` is -["user", "admin"] and the ``join_string`` is ":" then the -``$group_string`` variable will be set to "user:admin". - -:: - - ["join", "$group_string", "$groups", ":"] - - --------------------------------------------------------------------------------- - -lower ------ - -``lower $variable value`` - -$variable - This variable is assigned the result of the lower operation. - -value - The value to lower case, may be either a string, array, or - associative array. - -**lower** lower cases the input value. The input value may be one of -the following types: - -string - The string is lower cased. - -array - Each member of the array must be a string, the result is an array - with the items replaced by their lower case value. - -associative array - Each key in the associative array is lower cased. The values - associated with the key are **not** modified. - -Examples: -^^^^^^^^^ - -Lookup ``UserName`` in the assertion and set the variable -``$username`` to it's lower case value. - -:: - - ["lower", "$username", "$assertion[UserName]"], - -Set each member of the ``$groups`` array to it's lower case value. If -``$groups`` was ["User", "Admin"] then ``$groups`` will become -["user", "admin"]. - -:: - - ["lower", "$groups", "$groups"], - -To enable case insensitive lookup's in an associative array lower case -each key in the associative array. If ``$assertion`` was {"UserName": -"JoeUser"} then ``$assertion`` will become {"username": "JoeUser"} - -:: - - ["lower", "$assertion", $assertion"] - --------------------------------------------------------------------------------- - -upper ------ - -``upper $variable value`` - -$variable - This variable is assigned the result of the upper operation. - -value - The value to upper case, may be either a string, array, or - associative array. - -**upper** is exactly analogous to `lower`_ except the values are upper -cased, see `lower`_ for details. - - --------------------------------------------------------------------------------- - -in --- - -``in member collection`` - -member - The value whose membership is being tested. - -collection - A collection of members. May be string, array or associative array. - -**in** tests to see if ``member`` is a member of ``collection``. The -membership test depends on the type of collection, the following are -supported: - -array - If any item in the array is equal to ``member`` then the result is - success. - -associative array - If the associative array contains a key equal to ``member`` then - the result is success. - -string - If the string contains a sub-string equal to ``member`` then the - result is success. - -Examples: -^^^^^^^^^ - -Test to see if the assertion contains a UserName value. - -:: - - ["in", "UserName", "$assertion"] - ["continue", "if_not_success"] - -Test to see if a group is one of "user" or "admin". - -:: - - ["in", "$group", ["user", "admin"]] - ["continue", "if_not_success"] - -Test to see if the sub-string "BigCorp" is in -the assertion's ``Provider`` value. - -:: - - ["in", "BigCorp", "$assertion[Provider]"] - ["continue", "if_not_success"] - - --------------------------------------------------------------------------------- - -not_in ------- - -``in member collection`` - -member - The value whose membership is being tested. - -collection - A collection of members. May be string, array or associative array. - -**not_in** is exactly analogous to `in`_ except the sense of the test -is reversed. See `in`_ for details. - --------------------------------------------------------------------------------- - -compare -------- - -``compare left operator right`` - -left - The left hand value of the binary operator. - -operator - The binary operator used for comparing left to right. - -right - The right hand value of the binary operator. - - -**compare** compares the left value to the right value according the -operator and sets success if the comparison evaluates to True. The -following relational operators are supported. - -+----------+-----------------------+ -| Operator | Description | -+==========+=======================+ -| == | equal | -+----------+-----------------------+ -| != | not equal | -+----------+-----------------------+ -| < | less than | -+----------+-----------------------+ -| <= | less than or equal | -+----------+-----------------------+ -| > | greater than | -+----------+-----------------------+ -| >= | greater than or equal | -+----------+-----------------------+ - - -The left and right hand sides of the comparison operator *must* be -the same type, no type conversions are performed. Not all combinations -of operator and type are supported. The table below illustrates the -supported combinations. Essentially you can test for equality or -inequality on any type. But only strings and numbers support the -magnitude relational operators. - - -+----------+--------+---------+------+---------+-----+------+------+ -| Operator | STRING | INTEGER | REAL | BOOLEAN | MAP | LIST | NULL | -+==========+========+=========+======+=========+=====+======+======+ -| == | X | X | X | X | X | X | X | -+----------+--------+---------+------+---------+-----+------+------+ -| != | X | X | X | X | X | X | X | -+----------+--------+---------+------+---------+-----+------+------+ -| < | X | X | X | | | | | -+----------+--------+---------+------+---------+-----+------+------+ -| <= | X | X | X | | | | | -+----------+--------+---------+------+---------+-----+------+------+ -| > | X | X | X | | | | | -+----------+--------+---------+------+---------+-----+------+------+ -| >= | X | X | X | | | | | -+----------+--------+---------+------+---------+-----+------+------+ - - -Examples: -^^^^^^^^^ - -Test to see if the ``$groups`` array has at least 2 members - -:: - - ["length", "$group_length", "$groups"], - ["compare", "$group_length", ">=", 2] - - --------------------------------------------------------------------------------- - -exit ----- - -``exit status criteria`` - -status - The result for the rule. - -criteria - The criteria upon which will cause the rule will be immediately - exited with a failed status. - -**exit** causes the rule being executed to immediately exit and a rule -result if the specified criteria is met. Statement verbs such as `in`_ -or `compare`_ set the result status which may be tested with the -``success`` and ``not_success`` criteria. - -The exit ``status`` may be one of: - -rule_fails - The rule has failed and no mapping will occur. - -rule_succeeds - The rule succeeded and the mapping will be applied. - -The ``criteria`` may be one of: - -if_success - If current result status is success then exit with ``status``. - -if_not_success - If current result status is not success then exit with ``status``. - -always - Unconditionally exit with ``status``. - -never - Effectively a no-op. Useful for debugging. - -Examples: -^^^^^^^^^ - -The rule requires ``UserName`` to be in the assertion. - -:: - - ["in", "UserName", "$assertion"] - ["exit", "rule_fails", "if_not_success"] - --------------------------------------------------------------------------------- - - -continue --------- - -``continue criteria`` - -criteria - The criteria which causes the remainder of the *block* to be - skipped. - -**continue** is used to control execution for statement blocks. It -mirrors in a crude way the `if` expression in a procedural -language. ``continue`` does *not* affect the success or failure of a -rule, rather it controls whether subsequent statements in a block are -executed or not. Control continues at the next statement block. - -Statement verbs such as `in`_ or `compare`_ set the result status -which may be tested with the ``success`` and ``not_success`` criteria. - -The criteria may be one of: - -if_success - If current result status is success then exit the statement - block and continue execution at the next statement block. - -if_not_success - If current result status is not success then exit the statement - block and continue execution at the next statement block. - -always - Immediately exit the statement block and continue execution at the - next statement block. - -never - Effectively a no-op. Useful for debugging. Execution continues at - the next statement. - -Examples: -^^^^^^^^^ - -The following pseudo code: - -:: - - roles = []; - if ("Groups" in assertion) { - groups = assertion["Groups"].split(":"); - if ("qa_test" in groups) { - roles.append("tester"); - } - } - -could be implemented this way: - -:: - - [ - ["set", "$roles", []], - ["in", "Groups", "$assertion"], - ["continue", "if_not_success"], - ["split" "$groups", $assertion[Groups]", ":"], - ["in", "qa_test", "$groups"], - ["continue", "if_not_success"], - ["append", "$roles", "tester"] - ] diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.png b/odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.png deleted file mode 100644 index 728b86cebae2353c42ab382e9e95ab417479cd80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38693 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 ServletContainer: list topologies -note right of Client -(Authorization = access token) -end note -ServletContainer -> TokenAuthFilter: access token -loop foreach TokenAuth - TokenAuthFilter -> TokenAuth: validate(token) - TokenAuth -> TokenAuth: validateToken -end -TokenAuth -> TokenAuthFilter: Authentication -note left of TokenAuth -(user/domain/roles/expiration) -end note -TokenAuthFilter -> AuthenticationService: set(Authentication) -TokenAuthFilter -> RestConf: list topologies -RestConf -> AuthenticationService: get: Authentication \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.diag b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.diag deleted file mode 100644 index 28317393..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.diag +++ /dev/null @@ -1,6 +0,0 @@ -blockdiag { - User <-> AAA; - User [numbered = 1, shape = actor] - AAA [numbered = 2, label = "App Server\nAAA"] -} - diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.svg b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.svg deleted file mode 100644 index 4056b10a..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - blockdiag - blockdiag { - User <-> AAA; - User [numbered = 1, shape = actor] - AAA [numbered = 2, label = "App Server\nAAA"] -} - - - - - - - - - 1 - - App Server - AAA - - 2 - - - - diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.diag b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.diag deleted file mode 100644 index 2076dd16..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.diag +++ /dev/null @@ -1,18 +0,0 @@ -blockdiag { - span_width = 30 - User <-> Apache; - Proxy <-> AAA; - group { - Apache <-> Proxy; - group { - orientation = portrait - Apache <-> SSSD; - } - } - User [numbered = 1, shape = actor, width = 60] - Apache [numbered = 2, label = "Apache\nAuthenticates user"] - SSSD [numbered = 3, label = "SSSD\nProvides user info"] - Proxy [numbered = 4, label = "Proxy Transport\nRequest + Metadata"] - AAA [numbered = 5, label = "App Server\nAAA"] -} - diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.svg b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.svg deleted file mode 100644 index 42196b60..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.svg +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - blockdiag - blockdiag { - span_width = 30 - User <-> Apache; - Proxy <-> AAA; - group { - Apache <-> Proxy; - group { - orientation = portrait - Apache <-> SSSD; - } - } - User [numbered = 1, shape = actor, width = 60] - Apache [numbered = 2, label = "Apache\nAuthenticates user"] - SSSD [numbered = 3, label = "SSSD\nProvides user info"] - Proxy [numbered = 4, label = "Proxy Transport\nRequest + Metadata"] - AAA [numbered = 5, label = "App Server\nAAA"] -} - - - - - - - - - - - - - - 1 - - Apache - Authenticates user - - 2 - - SSSD - Provides user info - - 3 - - Proxy Transport - Request + Metadata - - 4 - - App Server - AAA - - 5 - - - - - - - - - - - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.diag b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.diag deleted file mode 100644 index 6ece3760..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.diag +++ /dev/null @@ -1,31 +0,0 @@ -seqdiag { - // Set edge properties - //edge_length = 300; // default value is 192 - //span_height = 80; // default value is 40 - - // Set fontsize. - //default_fontsize = 12; // default value is 11 - - // Numbering edges automaticaly - autonumber = False; - - // Change note color - default_note_color = lightblue; - - Client -> Apache [label = "Request"]; - === Apache mod_auth_kerb === - Client <- Apache [label = "401 Unauthorized"]; - Client -> Apache [label = "Authorization: Credentials"]; - Apache -> Apache [label = "Set\nUser Name\nAuth Type"]; - === Apache mod_lookup_identity === - Apache -> SSSD [label = "Get User Info"]; - SSSD --> IdP [label = "Get User Info", leftnote = "Only if\nnot cached\nby SSSD"]; - SSSD <-- IdP [label = "Return User Info"]; - Apache <- SSSD [label = "Return User Info"]; - Apache -> Apache [label = "Set User specific\nenvironment\nvariables"]; - === Apache mod_proxy === - Apache -> Container [label = "Proxy With User's Metadata"]; - Apache <- Container [label = "Response"]; - Client <- Apache [label = "Response"]; - -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.svg b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.svg deleted file mode 100644 index 91e8b1be..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.svg +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - blockdiag - seqdiag { - // Set edge properties - //edge_length = 300; // default value is 192 - //span_height = 80; // default value is 40 - - // Set fontsize. - //default_fontsize = 12; // default value is 11 - - // Numbering edges automaticaly - autonumber = False; - - // Change note color - default_note_color = lightblue; - - Client -> Apache [label = "Request"]; - === Apache mod_auth_kerb === - Client <- Apache [label = "401 Unauthorized"]; - Client -> Apache [label = "Authorization: Credentials"]; - Apache -> Apache [label = "Set\nUser Name\nAuth Type"]; - === Apache mod_lookup_identity === - Apache -> SSSD [label = "Get User Info"]; - SSSD --> IdP [label = "Get User Info", leftnote = "Only if\nnot cached\nby SSSD"]; - SSSD <-- IdP [label = "Return User Info"]; - Apache <- SSSD [label = "Return User Info"]; - Apache -> Apache [label = "Set User specific\nenvironment\nvariables"]; - === Apache mod_proxy === - Apache -> Container [label = "Proxy With User's Metadata"]; - Apache <- Container [label = "Response"]; - Client <- Apache [label = "Response"]; - -} - - - - - - - - - - - - - - - - - - - - - - - - - - Client - - Apache - - SSSD - - IdP - - Container - - - - - - - - - - - - - - - - - - Only if - not cached - by SSSD - - - - - - - - - - - - - - - Request - 401 Unauthorized - Authorization: Credentials - Set - User Name - Auth Type - Get User Info - Get User Info - Return User Info - Return User Info - Set User specific - environment - variables - Proxy With User's Metadata - Response - Response - - - - - - Apache mod_auth_kerb - - - - - - Apache mod_lookup_identity - - - - - - Apache mod_proxy - diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.diag b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.diag deleted file mode 100644 index 8f69a0b8..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.diag +++ /dev/null @@ -1,25 +0,0 @@ -blockdiag { - Connector -> SssdFilter; - SssdFilter -> ClaimAuthFilter; - ClaimAuthFilter -> SssdClaimAuth; - SssdClaimAuth -> Assertion [folded]; - - group { - orientation = portrait - Assertion -> JsonAssertion; - JsonAssertion -> IdPMapper; - IdPMapper -> JsonMapped; - } - - JsonMapped -> Claim; - - Connector [numbered = 1] - SssdFilter [numbered = 2] - ClaimAuthFilter [numbered = 3] - SssdClaimAuth [numbered = 4] - Assertion [numbered = 4.1] - JsonAssertion [numbered = 4.2] - IdPMapper [numbered = 4.3] - JsonMapped [numbered = 4.4] - Claim [numbered = 5] -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.svg b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.svg deleted file mode 100644 index 74850a85..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - blockdiag - blockdiag { - Connector -> SssdFilter; - SssdFilter -> ClaimAuthFilter; - ClaimAuthFilter -> SssdClaimAuth; - SssdClaimAuth -> Assertion [folded]; - - group { - orientation = portrait - Assertion -> JsonAssertion; - JsonAssertion -> IdPMapper; - IdPMapper -> JsonMapped; - } - - JsonMapped -> Claim; - - Connector [numbered = 1] - SssdFilter [numbered = 2] - ClaimAuthFilter [numbered = 3] - SssdClaimAuth [numbered = 4] - Assertion [numbered = 4.1] - JsonAssertion [numbered = 4.2] - IdPMapper [numbered = 4.3] - JsonMapped [numbered = 4.4] - Claim [numbered = 5] -} - - - - - - - - - - - - - Connector - - 1 - - SssdFilter - - 2 - - ClaimAuthFilter - - 3 - - SssdClaimAuth - - 4 - - Assertion - - 4.1 - - JsonAssertion - - 4.2 - - IdPMapper - - 4.3 - - JsonMapped - - 4.4 - - Claim - - 5 - - - - - - - - - - - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_05.svg b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_05.svg deleted file mode 100644 index f4657f06..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_05.svg +++ /dev/null @@ -1,613 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - Apache mod_proxy:forward port 8383 - - - - Connector:port = 80(web) - - - - Connector:port = 8383(auth proxy) - - - - - - - - - - - - AAA Servletexecuteswith roles - - - Non-AAAServlet - - - - ClaimAuthFilter:localPort insecureProxyPorts? - - - No - - - - Yes - - - - - - - - - - - - Java EE Container - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.png b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.png deleted file mode 100644 index 9f9a0b496c7a40f51e7b14efe7903f96dba015a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39322 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 Apache WebServer: authenticate -note right of Client -credentials -end note -Apache WebServer -> SSSD: authenticate -SSSD -> LDAP/AD : authenticate -SSSD -> Apache WebServer: claim -Apache WebServer -> ServletContainer: CGI variables -ServletContainer -> SSSD Plugin: Servlet attributes/headers -SSSD Plugin -> SSSD Plugin : transformClaim -SSSD Plugin -> TokenEndPoint : claim -TokenEndPoint -> TokenEndPoint : createToken -TokenEndPoint -> Client : refresh token, list of authorized domains -Client -> TokenEndPoint : refresh token, domain -TokenEndPoint -> Client : access token diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_configuration.rst b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_configuration.rst deleted file mode 100644 index 7f912d94..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_configuration.rst +++ /dev/null @@ -1,1687 +0,0 @@ -################################################ -Federated Authentication Utilizing Apache & SSSD -################################################ - -:Author: John Dennis -:Email: jdennis@redhat.com - -.. contents:: Table of Contents - -************ -Introduction -************ - -Applications should not need to handle the burden of authentication -and authorization. These are complex technologies further complicated -by the existence of a wide variety of authentication -mechanisms. Likewise there are numerous identity providers (IdP) which -one may wish to utilize, perhaps in a federated manner. The potential -to make critical mistakes are high while consuming significant -engineering resources. Ideally an application should "outsource" it's -authentication to an "expert" and avoid unnecessary development costs. - -For web based applications (both conventional HTML and REST API) there -has been a trend to embed a simple HTTP server in the application or -application server which handles the HTTP requests eschewing the use -of a traditional web server such as Apache. - -.. figure:: sssd_01.png - :align: center - - _`Figure 1.` - -But traditional web servers have a lot of advantages. They often come -with extensive support for technologies you might wish to utilize in -your application. It would require signification software engineering -to add support for those technologies in your application. The problem -is compounded by the fact many of these technologies demand domain -expertise which is unlikely to be available in the application -development team. Another problem is the libraries needed to utilize -the technology may not even be available in the programming language -the application is being developed in. Fundamentally an application -developer should focus on developing their application instead of -investing resources into implementing complex code for the ancillary -technologies the application may wish to utilize. - -Therefore fronting your application with a web server such as Apache -makes a lot of sense. One should allow Apache to handle complex tasks -such as multiple authentication mechanisms talking to multiple -IdP's. Suppose you want your application to handle Single Sign-On -(SSO) via Kerberos or authentication based on X509 certificates -(i.e. PKI). Apache already has extensions to handle these which have -been field proven, it would be silly to try and support these in your -application. Apache also comes with other useful extensions such as -``mod_identity_lookup`` which can extract metadata about an -authenticated user from multiple sources such as LDAP, -Active Directory, NIS, etc. - -By fronting your application with Apache and allowing Apache to handle -the complex task of authentication, identity lookups etc. you've -greatly increased the features of your application while at the same -time reducing application development time along with increasing -application security and robustness. - -.. figure:: sssd_02.png - :align: center - - _`Figure 2.` - -When Apache fronts your application you will be passed the results of -authentication and identity lookups. Your application only needs a -simple mechanism to accept these values. There are a variety of ways -the values can be passed from Apache to your application which will be -discussed in later sections. - -Authentication & Identity Properties -==================================== - -Authentication is proving that a user is who they claim to be, in -other words after authentication the user has a proven identity. In -security parlance the authenticated entity is call a -principal. Principals may be humans, machines or -services. Authorization is distinct from authentication. Authorization -declares what actions an authenticated principal may perform. For -example, does a principal have permission to read a certain file, run -a specific command, etc. Identity metadata is typically bound to the -principal to provide extra information. Examples include the users -full name, their organization, the groups they are members of, etc. - -Apache can provide both authentication and identity metadata to an -application freeing the application of this task. Authorization -usually will remain the province of the application. A typical -design pattern is to assign roles to a principal based on identity -properties. As the application executes on behalf of a principal the -application will check if the principal has the necessary role needed -to perform the operation. - -Apache ships with a wide variety of authentication modules. After an -Apache authentication module successfully authenticates a principal, it -sets internal variables identifying the principal and the -authentication method used to authenticate the principal. These are -exported as the CGI variables REMOTE_USER and AUTH_TYPE respectively -(see `CGI Export Issues`_ for further information). - -Identity Properties -------------------- - -Most Apache authentication modules do not have access to any of the -identity properties bound to the authenticated principal. Those -identity properties must be provided by some other mechanism. Typical -mechanisms include lookups in LDAP, Active Directory, NIS, POSIX -passwd/gecos and SQL. Managing these lookups can be difficult -especially in a networked environment where services may be -temporarily unavailable and/or in a enterprise deployment where -identity sources must be multiplexed across a variety of services -according to enterprise wide policy. - -`SSSD`_ (System Security Services Daemon) is designed to alleviate many -of the problems surrounding authentication and identity property -lookup. SSSD can provide identity properties via D-Bus using it's -InfoPipe (IFP) feature. The `mod_identity_lookup`_ Apache module is -given the name of the authenticated principal and makes available -identity properties via Apache environment variables (see `Configure -SSSD IFP`_ for details). - -Exporting & Consuming Identity Metadata -======================================= - -The authenticated principal (REMOTE_USER), the mechanism used to -authenticate the principal (AUTH_TYPE) and identity properties -(supplied by SSSD IFP) are exported to the application which trusts -this metadata to be valid. - -How is this identity metadata exported from Apache and then be -consumed by a Java EE Servlet? - -The architectural design inside Apache tries to capitalize on the -existing CGI standard (`CGI RFC`_) as much as possible. CGI defines -these relevant environment variables: - - * REMOTE_USER - * AUTH_TYPE - * REMOTE_ADDR - * REMOTE_HOST - - -Transporting Identity Metadata from Apache to a Java EE Servlet -=============================================================== - -In following figure we can see that the user connects to Apache -instead of the servlet container. Apache authenticates the user, looks -up the principal's identity information and then proxies the request -to the servlet container. The additional identity metadata must be -included in the proxy request in order for the servlet to extract it. - -.. figure:: sssd_03.png - :align: center - - _`Figure 3.` - -The Java EE Servlet API is designed with the HTTP protocol in mind -however the servlet never directly accesses the HTTP protocol stream. -Instead it uses the servlet API to get access to HTTP request -data. The responsibility for HTTP communication rests with the -container's ``Connector`` objects. When the servlet API needs -information it works in conjunction with the ``Connector`` to supply -it. For example the ``HttpServletRequest.getRemoteHost()`` method -interrogates information the ``Connector`` placed on the internal -request object. Analogously ``HttpServletRequest.getRemoteUser()`` -interrogates information placed on the internal request object by an -authentication filter. - -But what happens when a HTTP request is proxied to a servlet container -by Apache and ``getRemoteHost()`` or ``getRemoteUser()`` is called? Most -``Connector`` objects do not understand the proxy scenario, to them -a request from a proxy looks just like a request sent directly to the -servlet container. Therefore ``getRemoteHost()`` or ``getRemoteUser()`` -ends up returning information relative to the proxy instead of the -user who connected to the proxy because it's the proxy who connected -to the servlet container and not the end user. There are 2 fundamental -approaches which allow the servlet API to return data supplied by the -proxy: - - 1. Proxy uses special protocol (e.g. AJP) to embed metadata. - 2. Metadata is embedded in an HTTP extension by the proxy (i.e. headers) - -Proxy With AJP Protocol ------------------------ - -The AJP_ protocol was designed as a protocol to exchange HTTP requests -and responses between Apache and a Java EE Servlet Container. One of -its design goals was to improve performance by translating common text -values appearing in HTTP requests to a more compact binary form. At -the same time AJP provided a mechanism to supply metadata about the -request to the servlet container. That metadata is encoded in an AJP -attribute (a name/value pair). The Apache AJP Proxy module looks up -information in the internal Apache request object (e.g. remote user, -remote address, etc.) and encodes that metadata in AJP attributes. On -the servlet container side a AJP ``Connector`` object is aware of these -metadata attributes, extracts them from the protocol and supplies -their values to the upper layers of the servlet API. Thus a call to -``HttpServletRequest.getRemoteUser()`` made by a servlet will receive -the value set by Apache prior to the proxy. This is the desired and -expected behavior. A servlet should be ignorant of the consequences of -proxies; the servlet API should behave the same regardless of the -presence of a proxy. - -The AJP protocol also has a general purpose attribute mechanism whereby -any arbitrary name/value pair can be passed. This proxy metadata can -be retrieved by a servlet by calling ``ServletRequest.getAttribute()`` -[1]_ When Apache mod_proxy_ajp is being used the authentication -metadata for the remote user and auth type are are automatically -inserted into the AJP protocol and the AJP ``Connector`` object on -the servlet receiving end supplies those values to -``HttpServletRequest.getRemoteHost()`` and -``HttpServletRequest.getRemoteUser()`` respectively. But the identity -metadata supplied by ``mod_identity_lookup`` needs to be explicitly -encoded into an AJP attribute (see `Configure SSSD IFP`_ for details) -that can later be retrieved by ``ServletRequest.getAttribute()``. - -Proxy With HTTP Protocol ------------------------- - -Although the AJP protocol offers a number of nice advantages sometimes -it's not an option. Not all servlet containers support AJP or there -may be some other deployment constraint that precludes its use. In this -case option 2 from above needs to be used. Option 2 requires only the -defined HTTP protocol be used without any "out of band" metadata. The -conventional way to attach extension metadata to a HTTP request is to -add extension HTTP headers. - -One problem with using extension HTTP headers to pass metadata to a -servlet is the expectation the servlet API will have the same -behavior. In other words the value returned by -``HttpServletRequest.getRemoteUser()`` should not depend on whether the -proxy request was exchanged with the AJP protocol or the HTTP -protocol. The solution to this is to wrap the ``HttpServletRequest`` -object in a servlet filter. The wrapper overrides certain request -methods (e.g. ``getRemoteUser()``). The override method looks to see if -the metadata is in the extension HTTP headers, if so it returns the -value found in the extension HTTP header otherwise it defers to the -existing servlet implementation. The ``ServletRequest.getAttribute()`` is -overridden in an analogous manner in the wrapper filter. Any call to -``ServletRequest.getAttribute()`` is first checked to see if the value -exists in the extension HTTP header first. - -Metadata supplied by Apache that is **not** part of the normal Java -EE Servlet API **always** appears to the servlet via the -``ServletRequest.getAttribute()`` method regardless of the proxy -transport mechanism. The consequence of this is a servlet -continues to utilize the existing Java EE Servlet API without concern -for intermediary proxies, *and* any other metadata supplied by a proxy -is *always* retrieved via ``ServletRequest.getAttribute()`` (see the -caveat about ``ServletRequest.getAttributeNames()`` [1]_). - -******************* -Configuration Guide -******************* - -Although Apache authentication and SSSD identity lookup can operate -with a variety of authentication mechanisms, IdP's and identity -metadata providers we will demonstrate a configuration example which -utilizes the FreeIPA_ IdP. FreeIPA excels at Kerberos SSO authentication, -Active Directory integration, LDAP based identity metadata storage and -lookup, DNS services, host based RBAC, SSH key management, certificate -management, friendly web based console, command line tools and many -other advanced IdP features. - -The following configuration steps will need to be performed: - -1. Install FreeIPA_ by following the installation guides in the FreeIPA_ - documentation area. When you install FreeIPA_ you will need to select a - realm (a.k.a domain) in which your users and hosts will exist. In - our example we will use the ``EXAMPLE.COM`` realm. - -2. Install and configure the Apache HTTP web server. The - recommendation is to install and run the Apache HTTP web server on - the same system the Java EE Container running AAA is installed on. - -3. Configure the proxy connector in the Java EE Container and set the - ``secureProxyPorts``. - -We will also illustrate the operation of the system by adding an -example user named ``testuser`` who will be a member of the -``odl_users`` and ``odl_admin`` groups. - -Add Example User and Groups to FreeIPA -====================================== - -After installing FreeIPA you will need to populate FreeIPA with your users, -groups and other data. Refer to the documentation in FreeIPA_ for the -variety of ways this task can be performed; it runs the gamut from web -based console to command line utilities. For simplicity we will use -the command line utilities. - -Identify yourself to FreeIPA as an administrator; this will give you the -necessary privileges needed to create and modify data in FreeIPA. You do -this by obtaining a Kerberos ticket for the ``admin`` user (or any -other user in FreeIPA with administrator privileges. - -:: - - % kinit admin@EXAMPLE.COM - -Create the example ``odl_users`` and `odl_admin`` groups. - -:: - - % ipa group-add odl_users --desc 'OpenDaylight Users' - % ipa group-add odl_admin --desc 'OpenDaylight Administrators' - -Create the example user ``testuser`` with the first name "Test" and a -last name of "User" and an email address of "test.user@example.com" - -:: - - % ipa user-add testuser --first Test --last User --email test.user@example.com - -Now add ``testuser`` to the ``odl_users`` and ``odl_admin`` groups. - -:: - - % ipa group-add-member odl_users --user testuser - % ipa group-add-member odl_admin --user testuser - -Configure Apache -================ - -A number of Apache configuration directives will need to be specified -to implement the Apache to application binding. Although these -configuration directives can be located in any number of different -Apache configuration files the most sensible approach is to co-locate -them in a single application configuration file. This greatly -simplifies the deployment of your application and isolates your -application configuration from other applications and services sharing -the Apache installation. In the examples that follow our application -will be named ``my_app`` and the Apache application configuration file -will be named ``my_app.conf`` which should be located in Apache's -``conf.d/`` directory. The web resource we are protecting and -supplying identity metadata for will be named ``my_resource``. - - -Configure Apache for Kerberos ------------------------------ - -When FreeIPA is deployed Kerberos is the preferred authentication mechanism -for Single Sign-On (SSO). FreeIPA also provides identity metadata via -Apache ``mod_identity_lookup``. To protect your ``my_resource`` resource -with Kerberos authentication identify your resource as requiring -Kerberos authentication in your ``my_app.conf`` Apache -configuration. For example: - -:: - - - AuthType Kerberos - AuthName "Kerberos Login" - KrbMethodNegotiate On - KrbMethodK5Passwd Off - KrbAuthRealms EXAMPLE.COM - Krb5KeyTab /etc/http.keytab - require valid-user - - -You will need to replace EXAMPLE.COM in the KrbAuthRealms declaration -with the Kerberos realm for your deployment. - - -Configure SSSD IFP ------------------- - -To use the Apache ``mod_identity_lookup`` module to supply identity -metadata you need to do the following in ``my_app.conf``: - -1. Enable the module - - :: - - LoadModule lookup_identity_module modules/mod_lookup_identity.so - -2. Apply the identity metadata lookup to specific URL's - (e.g. ``my_resource``) via an Apache location directive. In this - example we look up the "mail" attribute and assign it to the - REMOTE_USER_EMAIL environment variable. - - :: - - - LookupUserAttr mail REMOTE_USER_EMAIL - - -3. Export the environment variable via the desired proxy protocol, see - `Exporting Environment Variables to the Proxy`_ - -Exporting Environment Variables to the Proxy --------------------------------------------- - -First you need to decide which proxy protocol you're going to use, AJP -or HTTP and then determine the target address and port to proxy to. The -recommended configuration is to run both the Apache server and the -servlet container on the same host and to proxy requests over the -local loopback interface (see `Declaring the Connector Ports for -Authentication Proxies`_). In our examples we'll use port 8383. Thus -in ``my_app.conf`` add a proxy declaration. - -For HTTP Proxy - -:: - - ProxyPass / http://localhost:8383/ - ProxyPassReverse / http://localhost:8383/ - -For AJP Proxy - -:: - - ProxyPass / ajp://localhost:8383/ - ProxyPassReverse / ajp://localhost:8383/ - -AJP Exports -^^^^^^^^^^^ - -AJP automatically forwards REMOTE_USER and AUTH_TYPE making them -available to the ``HttpServletRequest`` API, thus you do not need to -explicitly forward these in the proxy configuration. However all other -``mod_identity_lookup`` metadata must be explicitly forwarded as an AJP -attribute. These AJP attributes become visible in the -``ServletRequest.getAttribute()`` method [1]_. - -The Apache ``mod_proxy_ajp`` module automatically sends any Apache -environment variable prefixed with "AJP\_" as an AJP attribute which -can be retrieved with ``ServletRequest.getAttribute()``. Therefore the -``mod_identity_lookup`` directives which specify the Apache environment -variable to set with the result of a lookup must be prefixed with -"AJP\_". Using the above example of looking up the principal's email -address we modify the environment variable to include the "AJP\_" -prefix. Thusly: - - :: - - - LookupUserAttr mail AJP_REMOTE_USER_EMAIL - - -The sequence of events is as follows: - - 1. When the URL matches "my_resource". - - 2. ``mod_identity_lookup`` retrieves the mail attribute for the - principal. - - 3. ``mod_identity_lookup`` assigns the value of the mail attribute - lookup to the AJP_REMOTE_USER_EMAIL Apache environment variable. - - 4. ``mod_proxy_ajp`` encodes AJP_REMOTE_USER_EMAIL environment - variable into an AJP attribute in the AJP protocol because the - environment variable is prefixed with "AJP\_". The name of the - attribute is stripped of it's "AJP\_" prefix thus the - AJP_REMOTE_USER_EMAIL environment variable is transferred as the - AJP attribute REMOTE_USER_EMAIL. - - 5. The request is forwarded (i.e. proxied) to servlet container - using the AJP protocol. - - 6. The servlet container's AJP ``Connector`` object is assigned each AJP - attribute to the set of attributes on the ``ServletRequest`` - attribute list. Thus a call to - ``ServletRequest.getAttribute("REMOTE_USER_EMAIL")`` yields the - value set by ``mod_identity_lookup``. - - -HTTP Exports -^^^^^^^^^^^^ - -When HTTP proxy is used there are no automatic or implicit metadata -transfers; every metadata attribute must be explicitly handled on both -ends of the proxy connection. All identity metadata attributes are -transferred as extension HTTP headers, by convention those headers are -prefixed with "X-SSSD-". - -Using the original example of looking up the principal's email -address we must now perform two independent actions: - - 1. Lookup the value via ``mod_identity_lookup`` and assign to an - Apache environment variable. - - 2. Export the environment variable in the request header with the - "X-SSSD-" prefix. - - :: - - - LookupUserAttr mail REMOTE_USER_EMAIL - RequestHeader set X-SSSD-REMOTE_USER_EMAIL %{REMOTE_USER_EMAIL}e - - -The sequence of events is as follows: - - 1. When the URL matches "my_resource". - - 2. ``mod_identity_lookup`` retrieves the mail attribute for the - principal. - - 3. ``mod_identity_lookup`` assigns the value of the mail attribute - lookup to the REMOTE_USER_EMAIL Apache environment variable. - - 4. Apache's RequestHeader directive executes just prior to the - request being forwarded (i.e. in the Apache fixup stage). It adds - the header X-SSSD-REMOTE_USER_EMAIL and assigns the value for - REMOTE_USER_EMAIL found in the set of environment variables. It - does this because the syntax %{XXX} is a variable reference for - the name XXX and the 'e' appended after the closing brace - indicates the lookup is to be performed in the set of environment - variables. - - 5. The request is forwarded (i.e. proxied) to the servlet container - using the HTTP protocol. - - 6. When ``ServletRequest.getAttribute()`` is called the ``SssdFilter`` - wrapper intercepts the ``getAttribute()`` method. It looks for an - HTTP header of the same name with "X-SSSD-" prefixed to it. In - this case ``getAttribute("REMOTE_USER_EMAIL")`` causes the lookup of - "X-SSSD-REMOTE_USER_EMAIL" in the HTTP headers, if found that - value is returned. - -AJP Proxy Example Configuration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you are using AJP proxy to the Java EE Container on port 8383 your -``my_app.conf`` Apache configuration file will probably look like -this: - -:: - - - - ProxyPass / ajp://localhost:8383/ - ProxyPassReverse / ajp://localhost:8383/ - - LookupUserAttr mail AJP_REMOTE_USER_EMAIL " " - LookupUserAttr givenname AJP_REMOTE_USER_FIRSTNAME - LookupUserAttr sn AJP_REMOTE_USER_LASTNAME - LookupUserGroups AJP_REMOTE_USER_GROUPS ":" - - - -Note the specification of the colon separator for the -``LookupUserGroups`` operation. [3]_ - -HTTP Proxy Example Configuration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you are using a conventional HTTP proxy to the Java EE Container on -port 8383 your ``my_app.conf`` Apache configuration file will probably -look like this: - -:: - - - - ProxyPass / http://localhost:8383/ - ProxyPassReverse / http://localhost:8383/ - - RequestHeader set X-SSSD-REMOTE_USER expr=%{REMOTE_USER} - RequestHeader set X-SSSD-AUTH_TYPE expr=%{AUTH_TYPE} - RequestHeader set X-SSSD-REMOTE_HOST expr=%{REMOTE_HOST} - RequestHeader set X-SSSD-REMOTE_ADDR expr=%{REMOTE_ADDR} - - LookupUserAttr mail REMOTE_USER_EMAIL - RequestHeader set X-SSSD-REMOTE_USER_EMAIL %{REMOTE_USER_EMAIL}e - - LookupUserAttr givenname REMOTE_USER_FIRSTNAME - RequestHeader set X-SSSD-REMOTE_USER_FIRSTNAME %{REMOTE_USER_FIRSTNAME}e - - LookupUserAttr sn REMOTE_USER_LASTNAME - RequestHeader set X-SSSD-REMOTE_USER_LASTNAME %{REMOTE_USER_LASTNAME}e - - LookupUserGroups REMOTE_USER_GROUPS ":" - RequestHeader set X-SSSD-REMOTE_USER_GROUPS %{REMOTE_USER_GROUPS}e - - - -Note the specification of the colon separator for the -``LookupUserGroups`` operation. [3]_ - - -Configure Java EE Container Proxy Connector -=========================================== - -The Java EE Container must be configured to listen for connections -from the Apache web server. A Java EE Container specifies connections -via a ``Connector`` object. A ``Connector`` **must** be dedicated -**exclusively** for handling authenticated requests from the Apache -web server. The reason for this is explained in `The Proxy -Problem`_. In addition ``ClaimAuthFilter`` needs to validate that any -request it processes originated from the trusted Apache instance. This -is accomplished by dedicating one or more ports exclusively for use by -the trusted Apache server and enumerating them in the -``secureProxyPorts`` configuration as explained in `Locking Down the -Apache to Java EE Container Channel`_ and `Declaring the Connector -Ports for Authentication Proxies`_. - -Configure Tomcat Proxy Connector --------------------------------- - -The Tomcat Java EE Container defines Connectors in its ``server.xml`` -configuration file. - -:: - - - - -:address: - This should be the loopback address as explained `Locking Down the - Apache to Java EE Container Channel`_. - -:port: - In our examples we've been using port 8383 as the proxy port. The - exact port is not important but it must be consistent with the - Apache proxy port, the ``Connector`` declaration, and the port value - in ``secureProxyPorts``. - -:protocol: - As explained in `Transporting Identity Metadata from Apache to a - Java EE Servlet`_ you will need to decide if you are using HTTP or - AJP as the proxy protocol. In the example above the protocol is set - for HTTP, if you use AJP instead the protocol should instead be - "AJP/1.3". - -:tomcatAuthentication: - This boolean flag tells Tomcat whether Tomcat should perform - authentication on the incoming requests or not. Since authentication - is performed by Apache we do not want Tomcat to perform - authentication therefore this flag must be set to false. - -The AAA system needs to know which port(s) the trusted Apache proxy -will be sending requests on so it can trust the request authentication -metadata. See `Declaring the Connector Ports for Authentication -Proxies`_ for more information). Set ``secureProxyPorts`` in the -FederationConfiguration. - -:: - - secureProxyPorts=8383 - - -Configure Jetty Proxy Connector -------------------------------- - -The Jetty Java EE Container defines Connectors in its ``jetty.xml`` -configuration file. - -:: - - - - - - 127.0.0.1 - 8383 - 300000 - 2 - false - 8445 - federationConn - 20000 - 5000 - - - - -:host: - This should be the loopback address as explained `Locking Down the - Apache to Java EE Container Channel`_. - -:port: - In our examples we've been using port 8383 as the proxy port. The - exact port is not important but it must be consistent with the - Apache proxy port, the ``Connector`` declaration, and the port value - in ``secureProxyPorts``. - - -Note, values in Jetty XML can also be parameterized so that they may -be passed from property files or set on the command line. Thus -typically the port is set within Jetty XML, but uses the Property -element to be customizable. Thus the above ``host`` and ``port`` -properties could be specificed this way: - -:: - - - - - - - - - -The AAA system needs to know which port(s) the trusted Apache proxy -will be sending requests on so it can trust the request authentication -metadata. See `Declaring the Connector Ports for Authentication -Proxies`_ for more information). Set ``secureProxyPorts`` in the -FederationConfiguration. - -************************************************ -How Apache Identity Metadata is Processed in AAA -************************************************ - -`Figure 2.`_ and `Figure 3.`_ illustrates the fact the first stage in -processing a request from a user begins with Apache where the user is -authenticated and SSSD supplies additional metadata about the -user. The original request along with the metadata are subsequently -forwarded by Apache to the Java EE Container. `Figure 4.`_ illustrates -the processing inside the Java EE Container once it receives the -request on one of its secure connectors. - - -.. figure:: sssd_04.png - :align: center - - _`Figure 4.` - -:Step 1: - One or more Connectors have been configured to listen for requests - being forwarded from a trusted Apache instance. The Connector is - configured to communicate using either the HTTP or AJP protocols. - See `Exporting Environment Variables to the Proxy`_ for more - information on selecting a proxy transport protocol. - -:Step 2: - The identity metadata bound to the request needs to be extracted - differently depending upon whether HTTP or AJP is the transport - protocol. To allow later stages in the pipeline to be ignorant of - the transport protocol semantics the ``SssdFilter`` servlet filter - is introduced. The ``SssdFilter`` wraps the ``HttpServletRequest`` - class and intercepts calls which might return the identity - metadata. The wrapper in the filter looks in protocol specific - locations for the metadata. In this manner users of the - ``HttpServletRequest`` are isolated from protocol differences. - - -:Step 3: - - The ``ClaimAuthFilter`` is responsible for determining if identity - metadata is bound to the request. If so all identity metadata is - packaged into an assertion which is then handed off to - ``SssdClaimAuth`` which will transform the identity metadata in the - assertion into a AAA Claim which is the authorizing token for the user. - -:Step 4: - The ``SssdClaimAuth`` object is responsible for transforming the - external federated identity metadata provided by Apache and SSSD into - a AAA claim. The AAA claim is an authorization token which includes - information about the user plus a set of roles. These roles provide the - authorization to perform AAA tasks. Although how roles are assigned is - flexible the expectation is domain and/or group membership will be the - primary criteria for role assignment. Because deciding how to handle - external federated identity metadata is site and deployment specific - we need a loadable policy mechanism. This is accomplished by a set of - transformation rules which transforms the incoming IdP identity - metadata into a AAA claim. For greater clarity this important step is - broken down into smaller units in the shaded box in `Figure 4.`_. - -:Step 4.1: - `The Mapping Rule Processor`_ is designed to accept a JSON object - (set of key/value pairs) as input and emit a different JSON object - as output effectively operating as a transformation engine on - key/value pairs. - -:Step 4.2: - The input assertion is rewritten as a JSON object in the format - required by the Mapping Rule Processor. The JSON assertion is then - passed into the Mapping Rule Processor. - -:Step 4.3: - `The Mapping Rule Processor`_ identified as ``IdPMapper`` evaluates - the input JSON assertion in the context of the mapping rules defined - for the site deployment. If ``IdPMapper`` is able to successfully - transform the input it will return a JSON object which we called the - *mapped* result. If the input JSON assertion is not compatible with - the site specific rules loaded into the ``IdPMapper`` then NULL is - returned by the ``IdPMapper``. - -:Step 4.4: - If a mapped JSON object is returned by the ``IdPMapper`` the mapping - was successful. The values in the mapped result are re-written into - an AAA Claim token. - -How Apache Identity Metadata is Mapped to AAA Values -==================================================== - -A federated IdP supplies metadata in a form unique to the IdP. This is -called an assertion. That assertion must be transformed into a format -and data understood by AAA. More importantly that assertion needs to -yield *authorization roles specific to AAA*. In `Figure 4.`_ Step 4.3 -the ``IdPMapper`` provides the transformation from an external IdP -assertion to an AAA specific claim. It does this via a Mapping Rule -Processor which reads a site specific set of transformation -rules. These mapping rules define how to transform an external IdP -assertion into a AAA claim. The mapping rules also are responsible for -validating the external IdP claim to make sure it is consistent with -the site specific requirements. The operation of the Mapping Rule -Processor and the syntax of the mapping rules are defined in `The -Mapping Rule Processor`_. - -Below is an example mapping rule which might be loaded into the -Mapping Rule Processor. It is assumed there are two AAA roles which -may be assigned [4]_: - -``user`` - A role granting standard permissions for normal ODL users. - -``admin`` - A special role granting full administrative permissions. - -In this example assigning the ``user`` and ``admin`` roles -will be based on group membership in the following groups: - -``odl_users`` - Members of this group are normal ODL users with restricted permissions. - -``odl_admin`` - Members of this group are ODL administrators with permission to - perform all operations. - -Granting of the ``user`` and/or ``admin`` roles based on -membership in the ``odl_users`` and ``odl_admin`` is illustrated in -the follow mapping rule example which also extracts the user principal -and domain information in the preferred format for the site -(e.g. usernames are lowercase without domain suffixes and the domain -is uppercase and supplied separately). - -_`Mapping Rule Example 1.` - -:: - - 1 [ - 2 {"mapping": {"ClientId": "$client_id", - 3 "UserId": "$user_id", - 4 "User": "$username", - 5 "Domain": "$domain", - 6 "roles": "$roles", - 7 }, - 8 "statement_blocks": [ - 9 [ - 10 ["set", "$groups", []], - 11 ["set", "$roles", []] - 12 ], - 13 [ - 14 ["in", "REMOTE_USER", "$assertion"], - 15 ["exit", "rule_fails", "if_not_success"], - 16 ["regexp", "$assertion[REMOTE_USER]", "(?\\w+)@(?.+)"], - 17 ["exit", "rule_fails", "if_not_success"], - 18 ["lower", "$username", "$regexp_map[username]"], - 19 ["upper", "$domain", "$regexp_map[domain]"], - 20 ], - 21 [ - 22 ["in", "REMOTE_USER_GROUPS", "$assertion"], - 23 ["exit", "rule_fails", "if_not_success"], - 24 ["split", "$groups", "$assertion[REMOTE_USER_GROUPS]", ":"], - 25 ], - 26 [ - 27 ["in", "odl_users", "$groups"], - 28 ["continue", "if_not_success"], - 29 ["append", "$roles", "user"], - 30 ], - 31 [ - 32 ["in", "odl_admin", "$groups"], - 33 ["continue", "if_not_success"], - 34 ["append", "$roles", "admin"] - 35 ], - 36 [ - 37 ["unique", "$roles", "$roles"], - 38 ["length", "$n_roles", "$roles"], - 39 ["compare", "$n_roles", ">", 0], - 40 ["exit", "rule_fails", "if_not_success"], - 41 ], - 42 ] - 43 } - 44 ] - -:Line 1: - Starts a list of rules. In this example only 1 rule is defined. Each - rule is a JSON object containing a ``mapping`` and a required list - of ``statement_blocks``. The ``mapping`` may either be specified - inside a rule as it is here or may be referenced by name in a table - of mappings (this is easier to manage if you have a large number of - rules and small number of mappings). - -:Lines 2-7: - Defines the JSON mapped result. Each key maps to AAA claim. The - value is a rule variable whose value will be substituted if the rule - succeeds. Thus for example the AAA claim value ``User`` will be - assigned the value from the ``$username`` rule variable. -:Line 8: - Begins the list of statement blocks. A statement must be contained - inside a block. -:Lines 9-12: - The first block usually initializes variables that will be - referenced later. Here we initialize ``$groups`` and ``$roles`` to - empty arrays. These arrays may be appended to in later blocks and - may be referenced in the final ``mapping`` output. -:Lines 13-20: - This block sets the user and domain information based on - ``REMOTE_USER`` and exits the rule if ``REMOTE_USER`` is not defined. -:Lines 14-15: - This test is critical, it assures ``REMOTE_USER`` is defined in the - assertion, if not the rule is skipped because we depend on - ``REMOTE_USER``. -:Lines 16-17: - Performs a regular expression match against ``REMOTE_USER`` to split - the username from the domain. The regular expression uses named - groups, in this instance ``username`` and ``domain``. If the regular - expression does not match the rule is skipped. -:Lines 18-19: - These lines reference the previous result of the regular expression - match which are stored in the special variable ``$regexp_map``. The - username is converted to lower case and stored in ``$username`` and - the domain is converted to upper case and stored in ``$domain``. The - choice of case is purely by convention and site requirements. -:Lines 21-35: - These 3 blocks assign roles based on group membership. -:Lines 21-25: - Assures ``REMOTE_USER_GROUPS`` is defined in the assertion; if not, the - rule is skipped. ``REMOTE_USER_GROUPS`` is colon separated list of group - names. In order to operate on the individual group names appearing - in ``REMOTE_USER_GROUPS`` line 24 splits the string on the colon - separator and stores the result in the ``$groups`` array. -:Lines 27-30: - This block assigns the ``user`` role if the user is a member of the - ``odl_users`` group. -:Lines 31-35: - This block assigns the ``admin`` role if the user is a - member of the ``odl_admin`` group. -:Lines 36-41: - This block performs final clean up actions for the rule. First it - assures there are no duplicates in the ``$roles`` array by calling - the ``unique`` function. Then it gets a count of how many items are - in the ``$roles`` array and tests to see if it's empty. If there are - no roles assigned the rule is skipped. -:Line 43: - This is the end of the rule. If we reach the end of the rule it - succeeds. When a rule succeeds the mapping associated with the rule - is looked up. Any rule variable appearing in the mapping is - substituted with its value. - -Using the rules in `Mapping Rule Example 1.`_ and following example assertion -in JSON format: - -_`Assertion Example 1.` - -:: - - { - "REMOTE_USER": "TestUser@example.com", - "REMOTE_AUTH_TYPE": "Negotiate", - "REMOTE_USER_GROUPS": "odl_users:odl_admin", - "REMOTE_USER_EMAIL": "test.user@example.com", - "REMOTE_USER_FIRSTNAME": "Test", - "REMOTE_USER_LASTNAME": "User" - } - -Then the mapper will return the following mapped JSON document. This -is the ``mapping`` defined on line 2 of `Mapping Rule Example 1.`_ with the -variables substituted after the rule successfully executed. Note any -valid JSON data type can be returned, in this example the ``null`` -value is returned for ``ClientId`` and ``UserId``, normal strings for -``User`` and ``Domain`` and an array of strings for the ``roles`` value. - -_`Mapped Result Example 1.` - -:: - - { - "ClientId": null, - "UserId": null, - "User": "testuser", - "Domain": "EXAMPLE.COM", - "roles": ["user", "admin"] - } - - -************************** -The Mapping Rule Processor -************************** - -The Mapping Rule Processor is designed to be as flexible and generic -as possible. It accepts a JSON object as input and returns a JSON -object as output. JSON was chosen because virtually all data can be -represented in JSON, JSON has extensive support and JSON is human -readable. The rules loaded into the Mapping Rule Processor are also -expressed in JSON. One advantage of this is it makes it easy for a -site administrator to define hardcoded values which are always -returned and/or static tables of white and black listed users or users -who are always mapped into certain roles. - -.. include:: mapping.rst - -*********************** -Security Considerations -*********************** - -Attack Vectors -============== - -A Java EE Container fronted by Apache has by definition 2 major -components: - -* Apache -* Java EE Container - -Each of these needs to be secure in its own right. There is extensive -documentation on securing each of these components and the reader is -encouraged to review this material. For the purpose of this discussion -we are most interested in how Apache and the Java EE -Container cooperate to form an integrated security system. Because -Apache is performing authentication on behalf of the Java EE Container, -it views Apache as a trusted partner. Our primary concern is the -communication channel between Apache and the Java EE Container. We -must assure the Java EE Container knows who it's trusted partner is -and that it only accepts security sensitive data from that partner, -this can best be described as `The Proxy Problem`_. - -Forged REMOTE_USER ------------------- - -HTTP request handling is often implemented as a processing pipeline -where individual handlers are passed the request, they may then attach -additional metadata to the request or transform it in some manner -before handing it off to the next stage in the pipeline. A request -handler may also short circuit the request processing pipeline and -cause a response to be generated. Authentication is typically -implemented an as early stage request handler. If a request gets past -an authentication handler later stage handlers can safely assume the -request belongs to an authenticated user. Authorization metadata may -also have been attached to the request. Later stage handlers use the -authentication/authorization metadata to make decisions as to whether -the operations in the request can be satisfied. - -When a request is fielded by a traditional web server with CGI (Common -Gateway Interface, RFC 3875) the request metadata is passed via CGI -meta-variables. CGI meta-variables are often implemented as environment -variables, but in practical terms CGI metadata is really just a set of -name/value pairs a later stage (i.e. CGI script, servlet, etc.) can -reference to learn information about the request. - -The CGI meta-variables REMOTE_USER and AUTH_TYPE relate to -authentication. REMOTE_USER is the identity of the authenticated user -and AUTH_TYPE is the authentication mechanism that was used to -authenticate the user. - -**If a later stage request handler sees REMOTE_USER and AUTH_TYPE as -non-null values it assumes the user is fully authenticated! Therefore -is it essential REMOTE_USER and AUTH_TYPE can only enter the request -pipeline via a trusted source.** - -The Proxy Problem -================= - -In a traditional monolithic web server the CGI meta-variables are -created and managed by the web server, which then passes them to CGI -scripts and executables in a very controlled environment where they -execute in the context of the web server. Forgery of CGI -meta-variables is generally not possible unless the web server has -been compromised in some fashion. - -However in our configuration the Apache web server acts as an identity -processor, which then forwards (i.e. proxies) the request to the Java -EE container (i.e Tomcat, Jetty, etc.). One could think of the Java -EE container as just another CGI script which receives CGI -meta-variables provided by the Apache web server. Where this analogy -breaks down is how Apache invokes the CGI script. Instead of forking a -child process where the child's environment and input/output pipes are -carefully controlled by Apache the request along with its additional -metadata is forwarded over a transport (typically TCP/IP) to another -process, the proxy, which listens on socket. - -The proxy (in this case the Java EE container) reads the request and -the attached metadata and acts upon it. If the request read by the -proxy contains the REMOTE_USER and AUTH_TYPE CGI meta-variables the -proxy will consider the request **fully authenticated!**. Therefore -when the Java EE container is configured as a proxy it is -**essential** it only reads requests from a **trusted** Apache web -server. If any other client aside from the trusted Apache web server -is permitted to connect to the Java EE container that client could -present forged REMOTE_USER and AUTH_TYPE meta-variables, which would be -automatically accepted as valid thus opening a huge security hole. - - -Possible Approaches to Lock Down a Proxy Channel -================================================ - -Tomcat Valves -------------- - -You can use a `Tomcat Remote Address Valve`_ valve to filter by IP or -hostname to only allow a subset of machines to connect. This can be -configured at the Engine, Host, or Context level in the -conf/server.xml by adding something like the following: - -:: - - - - - -The problem with valves is they are a Tomcat only concept, the -``RemoteAddrValve`` only checks addresses, not port numbers (although -it should be easy to add port checking) and they don't offer anything -better than what is described in `Locking Down the Apache to Java EE -Container Channel`_, which is not container specific. Servlet filters -are always available regardless of the container the servlet is -running in. A filter can check both the address and port number and -refuse to operate on the request if the address and port are not known to -be a trusted authentication proxy. Also note that if the Java EE -Container is configured to accept connections other than from the -trusted HTTP proxy server (a very likely scenario) then filtering at -the connector level is not sufficient because a servlet which trusts -``REMOTE_USER`` must be assured the request arrived only on a -trusted HTTP proxy server connection, not one of the other possible -connections. - -SSL/TLS with client auth ------------------------- - -SSL with client authentication is the ultimate way to lock down a HTTP -Server to Java EE Container proxy connection. SSL with client -authentication provides authenticity, integrity, and -confidentiality. However those desirable attributes come at a -performance cost which may be excessive. Unless a persistent TCP -connection is established between the HTTP server and the Java EE -Container a SSL handshake will need to occur on each request being -proxied, SSL handshakes are expensive. Given that the HTTP server and -the Java EE Container will likely be deployed on the same compute node -(or at a minimum on a secure subnet) the advantage of SSL for proxy -connections may not be warranted because other options are available -for these configuration scenarios; see `Locking Down the Apache to Java EE -Container Channel`_. Also note that if the Java EE -Container is configured to accept connections other than from the -trusted HTTP proxy server (a very likely scenario), then filtering at -the connector level is not sufficient because a servlet which trusts -``REMOTE_USER`` must be assured that the request arrived only on a -trusted HTTP proxy server connection, not one of the other possible -connections. - - -Java Security Manager Permissions ---------------------------------- - -The Java Security Manager allows you define permissions which are -checked at run time before code executes. -``java.net.SocketPermission`` and ``java.net.NetPermission`` would -appear to offer solutions for restricting which host and port a -request containing ``REMOTE_USER`` will be trusted. However security -permissions are applied *after* a request is accepted by a -connector. They are also more geared towards what connections code can -subsequently utilize as opposed to what connection a request was -presented on. Therefore security manager permissions seem to offer little -value for our purpose. One can simply test to see which host sent the -proxy request and on what port it arrived on by looking at the -connection information in the request. Restricting which proxies can -submit trusted requests is better handled at the level of the -connector, which unfortunately is a container implementation -issue. Tomcat and Jetty have different ways of handling connector -specifications. - -AJP requiredSecret ------------------- - -The AJP protocol includes an attribute called ``requiredSecret``, which -can be used to secure the connection between AJP endpoints. When an -HTTP server sends an AJP proxy request to a Java EE Container it -embeds in the protocol transmission a string (``requiredSecret``) -known only to the HTTP server and the Java EE Container. The AJP -connector on the Java EE Container is configured with the -``requiredSecret`` value and will reject as unauthorized any AJP -requests whose ``requiredSecret`` does not match. - -There are two problems with `requiredSecret``. First of all it's not -particularly secure. In fact, it's fundamentally no different than -sending a cleartext password. If the AJP request is not encrypted it -means the ``requiredSecret`` will be sent in the clear which is -probably one of the most egregious security mistakes. If the AJP -request is transmitted in a manner where the traffic can be sniffed, it -would be trivial to recover the ``requiredSecret`` and forge a request -with it. On the other hand encrypting the communication channel -between the HTTP server and the Java EE Container means using SSL -which is fairly heavyweight. But more to the point, if one is using -SSL to encrypt the channel there is a *far better* mechanism to ensure -the HTTP server is who it claims to be than embedding -``requiredSecret``. If one is using SSL you might as well use SSL -client authentication where the HTTP identifies itself via a client -certificate. SSL client authentication is a very robust authentication -mechanism. But doing SSL client authentication, or for that matter -just SSL encryption, for *every* AJP protocol request is prohibitively -expensive from a performance standpoint. - -The second problem with ``requiredSecret`` is that despite being documented -in a number of places it's not actually implemented in Apache -``mod_proxy_ajp``. This is detailed in `bug 53098`_. You can set -``requiredSecret`` in the ``mod_proxy_ajp`` configuration, but it won't -be included in the wire protocol. There is a patch to implement -``requiredSecret`` but, it hasn't made it into any shipping version of -Apache yet. But even if ``requiredSecret`` was implemented it's not -useful. Also one could construct the equivalent of ``requiredSecret`` -from other AJP attributes and/or an HTTP extension header but those -would suffer from the same security issues ``requiredSecret`` has, -therefore it's mostly pointless. - -Java EE Container Issues -======================== - -Jetty Issues ------------- - -Jetty is a Java EE Container which can be used -as alternative to Tomcat. Jetty is an Eclipse project. Recent versions -of Jetty have dropped support for AJP; this is described in the -`Jetty AJP Configuration Guide`_ which states: - - Configuring AJP13 Using mod_jk or mod_proxy_ajp. Support for this - feature has been dropped with Jetty 9. If you feel this should be - brought back please file a bug. - -Eclipse `Bug 387928`_ *Retire jetty-ajp* was opened to track the -removal of AJP in Jetty and is now closed. - -Tomcat Issues -------------- - -You should refer the `Tomcat Security How-To`_ for a full discussion -of Tomcat security issues. - -The tomcatAuthentication attribute is used with the AJP connectors to -determine if Tomcat should authenticate the user or if authentication -can be delegated to the reverse proxy that will then pass the -authenticated username to Tomcat as part of the AJP protocol. - -The requiredSecret attribute in AJP connectors configures a shared -secret between Tomcat and the reverse proxy in front of Tomcat. It is used -to prevent unauthorized connections over AJP protocol. - -Locking Down the Apache to Java EE Container Channel -==================================================== - -The recommended approach to lock down the proxy channel is: - - * Run both Apache and the servlet container on the same host. - - * Configure Apache to forward the proxy request on the loopback - interface (e.g. 127.0.0.1 also known as ``localhost``). This - prohibits any external IP address from connecting, only processes - running on the locked down host can communicate over - ``localhost``. - - * Reserve one or more ports for communication **exclusively** for - proxy communication between Apache and the servlet container. The - servlet container may listen on other ports for non-critical - non-authenticated requests. - - * The ``ClaimAuthFilter`` that reads the identity metadata **must** - assure that requests have arrived only on a **trusted port**. To - achieve this the ``FederationConfiguration`` defines the - ``secureProxyPorts`` configuration option. ``secureProxyPorts`` is - a space delimited list of ports which during deployment the - administrator has configured such that they are **exclusively** - dedicated for use by the Apache server(s) providing authentication - and identity information. These ports are set in the servlet - container's ``Connector`` declarations. See `Declaring the - Connector Ports for Authentication Proxies`_ for more - information). - - * When the ``ClaimAuthFilter`` receives a request, the first thing - it does is check the ``ServletRequest.getLocalPort()`` value and - verifies it is a member of the ``secureProxyPorts`` configuration - option. If the port is a member of ``secureProxyPorts``, it will - trust every identity assertion found in the request. If the local - port is not a member of ``secureProxyPorts``, a HTTP 401 - (unauthorized) error status will be returned for the request. A - warning message will be logged the first time this occurs. - - -Declaring the Connector Ports for Authentication Proxies --------------------------------------------------------- - -As described in `The Proxy Problem`_ the AAA authentication system -**must** confirm the request it is processing originated from a *trusted -HTTP proxy server*. This is accomplished with port isolation. - -The administrator deploying a federated AAA solution with SSSD -identity lookups must declare in the AAA federation configuration -which ports the proxy requests from the trusted HTTP server will -arrive on by setting the ``secureProxyPorts`` configuration -item. These ports **must** only be used for the trusted HTTP proxy -server. The AAA federation software will not perform authentication -for any request arriving on a port other than those listed in -``secureProxyPorts``. - -.. figure:: sssd_05.png - :align: center - - _`Figure 5.` - -``secureProxyPorts`` configuration option is set either in the -``federation.cfg`` file or in the -``org.opendaylight.aaa.federation.secureProxyPorts`` bundle -configuration. ``secureProxyPorts`` is a space-delimited list of port -numbers on which a trusted HTTP proxy performing authentication -forwards pre-authenticated requests. For example: - -:: - - secureProxyPorts=8383 - -Means a request which arrived on port 8383 is from a trusted HTTP -proxy server and the value of ``REMOTE_USER`` and other authentication -metadata in request can be trusted. - -######## -Appendix -######## - -***************** -CGI Export Issues -***************** - -Apache processes requests as a series of steps in a pipeline -fashion. The ordering of these steps is important. Core Apache is -fairly minimal, most of Apache's features are supplied by loadable -modules. When a module is loaded it registers a set of *hooks* -(function pointers) which are to be run at specific stages in the -Apache request processing pipeline. Thus a module can execute code at -any of a number of stages in the request pipeline. - -The user metadata supplied by Apache is initialized in two distinct -parts of Apache. - - 1. an authentication module (e.g. mod_auth_kerb) - 2. the ``mod_lookup_identity`` module. - -After successful authentication the authentication module will set the -name of the user principal and the mechanism used for authentication -in the request structure. - - * ``request->user`` - * ``request->ap_auth_type`` - -Authentication hooks run early in the request pipeline for the obvious -reason a request should not be processed if not authenticated. The -specific authentication module that runs is defined by ``Location`` -directive in the Apache configuration which binds specific -authentication to specific URL's. The ``mod_lookup_identity`` module -must run *after* authentication module runs because it depends on -knowing who the authenticated principal is so it can lookup the data -on that principal. - -When reading ``mod_lookup_identity`` documentation one often sees -references to the ``REMOTE_USER`` CGI environment variable with the -implication ``REMOTE_USER`` is how one accesses the name of the -authenticated principal. This is a bit misleading, ``REMOTE_USER`` is -a CGI environment variable. CGI environment variables are only set by -Apache when it believes the request is going to be processed by a CGI -implementation. In this case ``REMOTE_USER`` is initialized from the -``request->user`` value. - -How is the authenticated principal actually forwarded to our proxy? -=================================================================== - -If we are using the AJP proxy protocol the ``mod_proxy_ajp`` module -when preparing the proxy request will read the value of -``request->user`` and insert it into the ``SC_A_REMOTE_USER`` AJP -attribute. On the receiving end ``SC_A_REMOTE_USER`` will be extracted -from the AJP request and used to populate the value returned -by``HttpServletRequest.getRemoteUser()``. The exchange of the -authenticated principal when using AJP is transparent to both the -sender and receiver, nothing special needs to be done. See -`Transporting Identity Metadata from Apache to a Java EE Servlet`_ -for details on how metadata can be exchanged with the proxy. - -However, if AJP is not being used to proxy the request the -authenticated principal must be passed through some other mechanism, -an HTTP extension header is the obvious solution. The Apache -``mod_headers`` module can be used to add HTTP request headers to the -proxy request, for example: - -:: - - RequestHeader set MY_HEADER MY_VALUE - -Where does the value MY_VALUE come from? It can be hardcoded into the -``RequestHeader`` statement or it can reference an existing -environment variable like this: - -:: - - RequestHeader set MY_HEADER %{FOOBAR}e - -where the notation ``%{FOOBAR}e`` is the contents of the environment -variable FOOBAR. Thus we might expect we could do this: - -:: - - RequestHeader set REMOTE_USER %{REMOTE_USER}e - -The conundrum is the presumption the ``REMOTE_USER`` environment -variable has already been set at the time ``mod_headers`` executes the -``RequestHeader`` statement. Unfortunately this often is not the -case. - -The Apache environment variables ``REMOTE_USER`` and ``AUTH_TYPE`` are -set by the Apache function ``ap_add_common_vars()`` defined in -server/util_script.c. ``ap_add_common_vars()`` and is called by the -following modules: - - * mod_authnz_fcgi - * mod_proxy_fcgi - * mod_proxy_scgi - * mod_isapi - * mod_ext_filter - * mod_include - * mod_cgi - * mod_cgid - -Apache variables -================ - -Apache modules provide access to variables which can be referenced by -configuration directives. Unfortunately there isn't a lot of -uniformity to what the variables are and how they're referenced; it -mostly depends on how a given Apache module was implemented. As you -might imagine a bit of inconsistent historical cruft has accumulated -over the years, it can be confusing. The Apache Foundation is trying -to clean some of this up bringing uniformity to modules by utilizing -the common ``expr`` (expression) module `ap_expr`_. The idea being modules will -forgo their home grown expression syntax with its numerous quirks and -instead expose the common ``expr`` language. However this is a work in -progress and at the time of this writing only a few modules have acquired -``expr`` expression support. - -Among the existing Apache modules there currently are three different -sets of variables. - - 1. Server variables. - 2. Environment variables. - 3. SSL variables. - -Server variables (item 1) are names given to internal values. The set -of names for server variables and what they map to are defined by the -module implementing the server variable lookup. For example -``mod_rewrite`` has its own variable lookup implementation. - -Environment variables (item 2) are variables *exported* to a -subprocess. Internally they are stored in -``request->subprocess_env``. The most common use of environment -variables exported to a subprocess are the CGI variables. - -SSL variables are connection specific values describing the SSL -connection. The lookup is implemented by ``ssl_var_lookup()``, which -given a variable name looks in a variety of internal data structures to -find the matching value. - -The important thing to remember is **server variables != environment -variables**. This can be confusing because they often share the same -name. For example, there is the server variable ``REMOTE_USER`` and -there is the environment variable ``REMOTE_USER``. The environment -variable ``REMOTE_USER`` only exists if some module has called -``ap_add_common_vars()``. To complicate matters, some modules allow you -to access *server variables*, other modules allow you to access -*environment variables* and some modules provide access to both -*server variables* and *environment variables*. - -Coming back to our goal of setting an HTTP extension header to the -value of ``REMOTE_USER``, we observe that ``mod_headers`` provides the -needed ``RequestHeader`` operation to set a HTTP header in the -request. Looking at the documentation for ``RequestHeader`` we see a -value can be specified with one of the following lookups: - -%{VARNAME}e - The contents of the environment variable VARNAME. - -%{VARNAME}s - The contents of the SSL environment variable VARNAME, if mod_ssl is enabled. - -But wait! This only gives us access to *environment variables* and the -``REMOTE_USER`` environment variable is only set if -``ap_add_common_vars()`` is called by a module **after** an -authentication module runs! ``ap_add_common_vars()`` is usually only -invoked if the request is going to be passed to a CGI script. But -we're not doing CGI; instead we're proxying the request. The -likelihood the ``REMOTE_USER`` environment variable will be set is -quite low. See `Setting the REMOTE_USER environment variable`_. - -``mod_headers`` is the only way to set a HTTP extension header and -``mod_headers`` only gives you access to environment variables and the -``REMOTE_USER`` environment variable is not set. Therefore if we're -not using AJP and must depend on setting a HTTP extension header for -``REMOTE_USER``, we have a **serious problem**. - -But there is a solution; you can either try the machinations described -in `Setting the REMOTE_USER environment variable`_ or assure you're -running at least Apache version 2.4.10. In Apache 2.4.10 the -``mod_headers`` module added support for `ap_expr`_. `ap_expr`_ -provides access to *server variables* by using the ``%{VARIABLE}`` -notation. `ap_expr`_ also can lookup subprocess environment variables -and operating system environment variables using its ``reqenv()`` and -``osenv()`` functions respectively. - -Thus the simple solution for exporting the ``REMOTE_USER`` HTTP -extension header if you're running Apache 2.4.10 or later is: - -:: - - RequestHeader set X-SSSD-REMOTE_USER expr=%{REMOTE_USER} - -The ``expr=%{REMOTE_USER}`` in the above statement says pass -``%{REMOTE_USER}`` as an expression to `ap_expr`_, evaluate the -expression and return the value. In this case the expression -``%{REMOTE_USER}`` is very simple, just the value of the server -variables ``REMOTE_USER``. Because ``RequestHeader`` runs after -authentication ``request->user`` will have been set. - -Setting the REMOTE_USER environment variable -============================================ - -If you do a web search on how to export ``REMOTE_USER`` in a HTTP -extension header for a proxy you will discover this is a common -problem that has frustrated a lot of people [2]_. The usual advice seems to -be to use ``mod_rewrite`` with a look-ahead. In fact this is even -documented in the `mod_rewrite documentation for REMOTE_USER`_ which says: - - %{LA-U:variable} can be used for look-aheads which perform an - internal (URL-based) sub-request to determine the final value of - variable. This can be used to access variable for rewriting which is - not available at the current stage, but will be set in a later - phase. - - For instance, to rewrite according to the REMOTE_USER variable from - within the per-server context (httpd.conf file) you must use - %{LA-U:REMOTE_USER} - this variable is set by the authorization - phases, which come after the URL translation phase (during which - mod_rewrite operates). - -One suggested solution is this: - -:: - - RewriteCond %{LA-U:REMOTE_USER} (.+) - RewriteRule .* - [E=RU:%1] - RequestHeader set X_REMOTE_USER %{RU}e - -1. The RewriteCond with the %{LA-U:} construct performs an internal - redirect to obtain the value of ``REMOTE_USER`` *server variable*, - if that value is non-empty because the (.+) regular expression - matched the rewrite condition succeeds and the following - RewriteRule executes. - -2. The RewriteRule executes, the first parameter is a pattern, the - second parameter is the replacement which can be followed by - optional flags inside brackets. The .* pattern is a regular - expression that matches anything, the - replacement is a special - value which indicates no replacement is to be performed. In other - words the pattern and replacement are no-ops and the RewriteRule is - just being used for it's side effect defined in the flags. The - E=NAME:VALUE notation says set the NAME environment variable to - VALUE. In this case the environment variable is RU and the value is - %1. The documentation for RewriteRule tells us that %N are - back-references to the last matched RewriteCond pattern, in this - case it's the value of ``REMOTE_USER``. - -3. Finally ``RequestHeader`` sets the request header - ``X_REMOTE_USER`` to the value of the ``RU`` environment variable. - -Another suggested solution is this: - -:: - - RewriteRule .* - [E=REMOTE_USER:%{LA-U:REMOTE_USER}] - -The Problem with mod_rewrite lookahead --------------------------------------- - -I **do not recommend** using mod_rewrite's lookahead to gain access to -authentication data values. Although the above suggestions will work -to get access to ``REMOTE_USER`` it is *extremely inefficient* because -it causes Apache to reprocess the request with an internal -redirect. The documentation suggests a lookahead reference will cause -one internal redirect. However from examining Apache debug logs the -``mod_rewite`` lookahead caused ``mod_lookup_identity`` to be invoked -**11 times** while handling one request. If the ``mod_rewrite`` -lookahead is removed and another technique is used to get access to -``REMOTE_USER`` then ``mod_lookup_identity`` is invoked exactly once -as expected. - -But it's not just ``REMOTE_USER`` which we need access to, we also need -to reference ``AUTH_TYPE`` which has the identical issues associated -with ``REMOTE_USER``. If an equivalent ``mod_rewrite`` block is added -to the configuration for ``AUTH_TYPE`` so that both ``REMOTE_USER`` -and ``auth_type`` are resolved using a lookahead Apache appears to go -into an infinite loop and the request stalls. - -I tried to debug what was occurring when Apache was configured this way -and why it seemed to be executing the same code over and over but I -was not able to figure it out. My conclusion is **using mod_rewrite -lookahead's is not a viable solution!** Other web posts also make -reference to the inefficiency but they seem to be unaware of just how -bad it is. - -.. [1] - Tomcat has a bug/feature, not all attributes are enumerated by - getAttributeNames() therefore getAttributeNames() cannot be used to - obtain the full set of attributes. However if you know the name of - the attribute a priori you can call getAttribute() and obtain the - value. Therefore we maintain a list of attribute names - (httpAttributes) which will be used to call getAttribute() with so we - don't miss essential attributes. - - This is the Tomcat bug, note it is marked WONTFIX. Bug 25363 - - request.getAttributeNames() not working properly Status: RESOLVED - WONTFIX https://issues.apache.org/bugzilla/show_bug.cgi?id=25363 - - The solution adopted by Tomcat is to document the behavior in the - "The Apache Tomcat Connector - Reference Guide" under the JkEnvVar - property where is says: - - You can retrieve the variables on Tomcat as request attributes via - request.getAttribute(attributeName). Note that the variables send via - JkEnvVar will not be listed in request.getAttributeNames(). - -.. [2] - Some examples of posts concerning the export of ``REMOTE_USER`` include: - http://www.jaddog.org/2010/03/22/how-to-proxy-pass-remote_user/ and - http://serverfault.com/questions/23273/apache-proxy-passing-on-remote-user-to-backend-server/ - -.. [3] - The ``mod_lookup_identity`` ``LookupUserGroups`` option accepts an - optional parameter to specify the separator used to separate group - names. By convention this is normally the colon (:) character. In - our examples we explicitly specify the colon separator because the - mapping rules split the value found in ``REMOTE_USER_GROUPS`` on - the colon character. - -.. [4] - The example of using the `The Mapping Rule Processor`_ to establish - the set of roles assigned to a user based on group membership is - for illustrative purposes in order to show features of the - federated IdP and mapping mechanism. Role assignment in AAA may be - done in other ways. For example an unscoped token without roles can - be used to acquire a scoped token with roles by presenting it to - the appropriate REST API endpoint. In actual deployments this may - be preferable because it places the responsibility of deciding who - has what role/permission on what part of the controller/network - resources more in the hands of the SDN controller administrator - than the IdP administrator. - -.. _FreeIPA: http://www.freeipa.org/ - -.. _SSSD: https://fedorahosted.org/sssd/ - -.. _mod_identity_lookup: http://www.adelton.com/apache/mod_lookup_identity/ - -.. _AJP: http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html - -.. _Tomcat Security How-To: http://tomcat.apache.org/tomcat-7.0-doc/security-howto.html - -.. _The Apache Tomcat Connector - Generic HowTo: http://tomcat.apache.org/connectors-doc/generic_howto/printer/proxy.html - -.. _CGI RFC: http://www.ietf.org/rfc/rfc3875 - -.. _ap_expr: http://httpd.apache.org/docs/current/expr.html - -.. _mod_rewrite documentation for REMOTE_USER: http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritecond - -.. _bug 53098: https://issues.apache.org/bugzilla/show_bug.cgi?id=53098 - -.. _Jetty AJP Configuration Guide: http://wiki.eclipse.org/Jetty/Howto/Configure_AJP13 - -.. _Bug 387928: https://bugs.eclipse.org/bugs/show_bug.cgi?id=387928 - -.. _Tomcat Remote Address Valve: http://tomcat.apache.org/tomcat-7.0-doc/config/valve.html#Remote_Address_Filter diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Authentication.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Authentication.java deleted file mode 100644 index 25ba898b..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Authentication.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -/** - * An immutable authentication context. - * - * @author liemmn - */ -public interface Authentication extends Claim { - - /** - * Get the authentication expiration date/time in number of milliseconds - * since start of epoch. - * - * @return expiration milliseconds since start of UTC epoch - */ - long expiration(); - -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationException.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationException.java deleted file mode 100644 index d4621527..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -/** - * A catch-all authentication exception. - * - * @author liemmn - * - */ -public class AuthenticationException extends RuntimeException { - private static final long serialVersionUID = -187422301135305719L; - - public AuthenticationException(String msg) { - super(msg); - } - - public AuthenticationException(String msg, Throwable cause) { - super(msg, cause); - } - - public AuthenticationException(Throwable cause) { - super(cause); - } -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationService.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationService.java deleted file mode 100644 index 24ae9238..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationService.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -/** - * Authentication service to provide authentication context. - */ -public interface AuthenticationService { - /** - * Retrieve the current security context, or null if none exists. - * - * @return security context - */ - Authentication get(); - - /** - * Set the current security context. Only {@link TokenAuth} should set - * security context based on the authentication result. - * - * @param auth - * security context - */ - void set(Authentication auth); - - /** - * Clear the current security context. - */ - void clear(); - - /** - * Checks to see if authentication is enabled. - * - * @return true if it is, false otherwise - */ - boolean isAuthEnabled(); -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Claim.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Claim.java deleted file mode 100644 index 7d9a229a..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Claim.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -import java.util.Set; - -/** - * A claim typically provided by an identity provider after validating the - * needed identity and credentials. - * - * @author liemmn - * - */ -public interface Claim { - /** - * Get the id of the authorized client. If the id is an empty string, it - * means that the client is anonymous. - * - * @return id of the authorized client, or empty string if anonymous - */ - String clientId(); - - /** - * Get the user id. User IDs are system-created. - * - * @return unique user id - */ - String userId(); - - /** - * Get the user name. User names are externally created. - * - * @return unique user name - */ - String user(); - - /** - * Get the fully-qualified domain name. Domain names are externally created. - * - * @return unique domain name, or empty string for a claim tied to no domain - */ - String domain(); - - /** - * Get a set of user roles. Roles are externally created. - * - * @return set of user roles - */ - Set roles(); -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClaimAuth.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClaimAuth.java deleted file mode 100644 index 447ffb35..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClaimAuth.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -import java.util.Map; - -/** - * An interface for in-bound claim transformation. - * - * @author liemmn - * - */ -public interface ClaimAuth { - - /** - * Transform a map of opaque in-bound claims into a {@link Claim} object. An - * example of an opaque claim map entry is - * "USER_NAME" -> "joe". - *

- * If there is no applicable claim information for the current - * implementation, this method should return a null. - *

- * In-bound claims are extracted from HttpServletRequest attributes, - * headers, and CGI variables as documented per Servlet specs. - * - * @param claim - * opaque claim - * @return normalized claim, or null if not applicable - */ - Claim transform(Map claim); -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClientService.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClientService.java deleted file mode 100644 index c11eec1c..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClientService.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -/** - * A service for managing authorized clients to the controller. - * - * @author liemmn - * - */ -public interface ClientService { - - void validate(String clientId, String clientSecret) throws AuthenticationException; -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/CredentialAuth.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/CredentialAuth.java deleted file mode 100644 index 341e49ae..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/CredentialAuth.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -/** - * An interface for direct authentication with some given credentials. - * - * @author liemmn - */ -public interface CredentialAuth { - - /** - * Authenticate a claim with the given credentials and domain scope. - * - * @param cred - * credentials - * @throws AuthenticationException - * if failed authentication - * @return authenticated claim - */ - Claim authenticate(T cred) throws AuthenticationException; -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Credentials.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Credentials.java deleted file mode 100644 index 7d2f19e5..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Credentials.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -/** - * An interface to represent user credentials. - */ -public interface Credentials { -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreException.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreException.java deleted file mode 100644 index 026c11ce..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.api; - -/* - * @author - Sharon Aicler (saichler@cisco.com) - */ -public class IDMStoreException extends Exception { - - private static final long serialVersionUID = -7534127680943957878L; - - public IDMStoreException(Exception e) { - super(e); - } - - public IDMStoreException(String msg) { - super(msg); - } -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreUtil.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreUtil.java deleted file mode 100644 index 07dd522f..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.api; - -import javax.naming.OperationNotSupportedException; - -/* - * This class is a utility to construct the different elements keys for the different data stores. - * For not making mistakes around the code constructing an element key, this class standardize the - * way the key is constructed to be used by the different data stores. - * - * @author - Sharon Aicler (saichler@cisco.com) - */ - -public class IDMStoreUtil { - private IDMStoreUtil() throws OperationNotSupportedException { - throw new OperationNotSupportedException(); - } - - public static String createDomainid(String domainName) { - return domainName; - } - - public static String createUserid(String username, String domainid) { - return username + "@" + domainid; - } - - public static String createRoleid(String rolename, String domainid) { - return rolename + "@" + domainid; - } - - public static String createGrantid(String userid, String domainid, String roleid) { - return userid + "@" + roleid + "@" + domainid; - } -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IIDMStore.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IIDMStore.java deleted file mode 100644 index 7b031e05..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IIDMStore.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.api; - -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Domains; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Grants; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.Roles; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.aaa.api.model.Users; - -/** - * @author - Sharon Aicler (saichler@cisco.com) - **/ -public interface IIDMStore { - public String DEFAULT_DOMAIN = "sdn"; - - // Domain methods - public Domain writeDomain(Domain domain) throws IDMStoreException; - - public Domain readDomain(String domainid) throws IDMStoreException; - - public Domain deleteDomain(String domainid) throws IDMStoreException; - - public Domain updateDomain(Domain domain) throws IDMStoreException; - - public Domains getDomains() throws IDMStoreException; - - // Role methods - public Role writeRole(Role role) throws IDMStoreException; - - public Role readRole(String roleid) throws IDMStoreException; - - public Role deleteRole(String roleid) throws IDMStoreException; - - public Role updateRole(Role role) throws IDMStoreException; - - public Roles getRoles() throws IDMStoreException; - - // User methods - public User writeUser(User user) throws IDMStoreException; - - public User readUser(String userid) throws IDMStoreException; - - public User deleteUser(String userid) throws IDMStoreException; - - public User updateUser(User user) throws IDMStoreException; - - public Users getUsers() throws IDMStoreException; - - public Users getUsers(String username, String domain) throws IDMStoreException; - - // Grant methods - public Grant writeGrant(Grant grant) throws IDMStoreException; - - public Grant readGrant(String grantid) throws IDMStoreException; - - public Grant deleteGrant(String grantid) throws IDMStoreException; - - public Grants getGrants(String domainid, String userid) throws IDMStoreException; - - public Grants getGrants(String userid) throws IDMStoreException; - - public Grant readGrant(String domainid, String userid, String roleid) throws IDMStoreException; -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IdMService.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IdMService.java deleted file mode 100644 index 1d698da5..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IdMService.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -import java.util.List; - -/** - * A service to provide identity information. - * - * @author liemmn - * - */ -public interface IdMService { - /** - * List all domains that the given user has at least one role on. - * - * @param userId - * id of user - * @return list of all domains that the given user has access to - */ - List listDomains(String userId); - - /** - * List all roles that the given user has on the given domain. - * - * @param userId - * id of user - * @param domain - * domain - * @return list of roles - */ - List listRoles(String userId, String domain); -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/PasswordCredentials.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/PasswordCredentials.java deleted file mode 100644 index e5fa346d..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/PasswordCredentials.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -/** - * Good 'ole username/password. - */ -public interface PasswordCredentials extends Credentials { - String username(); - - String password(); - - String domain(); -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/SHA256Calculator.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/SHA256Calculator.java deleted file mode 100644 index 81f4b899..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/SHA256Calculator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.api; - -import java.security.MessageDigest; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Sharon Aicler (saichler@cisco.com) - */ -public class SHA256Calculator { - - private static final Logger LOG = LoggerFactory.getLogger(SHA256Calculator.class); - - private static MessageDigest md = null; - private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private static WriteLock writeLock = lock.writeLock(); - - public static String generateSALT() { - StringBuffer salt = new StringBuffer(); - for (int i = 0; i < 12; i++) { - int random = (int) (Math.random() * 24 + 1); - salt.append((char) (65 + random)); - } - return salt.toString(); - } - - public static String getSHA256(byte data[], String salt) { - byte SALT[] = salt.getBytes(); - byte temp[] = new byte[data.length + SALT.length]; - System.arraycopy(data, 0, temp, 0, data.length); - System.arraycopy(SALT, 0, temp, data.length, SALT.length); - - if (md == null) { - try { - writeLock.lock(); - if (md == null) { - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (Exception err) { - LOG.error("Error calculating SHA-256 for SALT", err); - } - } - } finally { - writeLock.unlock(); - } - } - - byte by[] = null; - - try { - writeLock.lock(); - md.update(temp); - by = md.digest(); - } finally { - writeLock.unlock(); - } - return removeSpecialCharacters(new String(by)); - } - - public static String getSHA256(String password, String salt) { - return getSHA256(password.getBytes(), salt); - } - - public static String removeSpecialCharacters(String str) { - StringBuilder buff = new StringBuilder(); - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) != '\'' && str.charAt(i) != 0) { - buff.append(str.charAt(i)); - } - } - return buff.toString(); - } -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenAuth.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenAuth.java deleted file mode 100644 index bbf6fa2b..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenAuth.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -import java.util.List; -import java.util.Map; - -/** - * An interface for in-bound token authentication. - * - * @author liemmn - */ -public interface TokenAuth { - - /** - * Validate the given token contained in the in-bound headers. - *

- * If there is no token signature in the given headers for this - * implementation, this method should return a null. If there is an - * applicable token signature, but the token validation fails, this method - * should throw an {@link AuthenticationException}. - * - * @param headers - * headers containing token to validate - * @return authenticated context, or null if not applicable - * @throws AuthenticationException - * if authentication fails - */ - Authentication validate(Map> headers) throws AuthenticationException; - -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenStore.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenStore.java deleted file mode 100644 index 4cd7aa78..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenStore.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api; - -/** - * A datastore for auth tokens. - * - * @author liemmn - * - */ -public interface TokenStore { - void put(String token, Authentication auth); - - Authentication get(String token); - - boolean delete(String token); - - long tokenExpiration(); -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Claim.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Claim.java deleted file mode 100644 index 180bddfb..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Claim.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import java.util.List; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "Claim") -public class Claim { - private String domainid; - private String userid; - private String username; - private List roles; - - public String getDomainid() { - return domainid; - } - - public void setDomainid(String id) { - this.domainid = id; - } - - public String getUserid() { - return userid; - } - - public void setUserid(String id) { - this.userid = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(String name) { - this.username = name; - } - - public List getRoles() { - return roles; - } - - public void setRoles(List roles) { - this.roles = roles; - } - -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domain.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domain.java deleted file mode 100644 index a42e0b6d..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domain.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "domain") -public class Domain { - private String domainid; - private String name; - private String description; - private Boolean enabled; - - public String getDomainid() { - return domainid; - } - - public void setDomainid(String id) { - this.domainid = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Boolean isEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - @Override - public int hashCode() { - return this.name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - Domain other = (Domain) obj; - if (other == null) - return false; - if (compareValues(getName(), other.getName()) - && compareValues(getDomainid(), other.getDomainid()) - && compareValues(getDescription(), other.getDescription())) - return true; - return false; - } - - private boolean compareValues(Object a, Object b) { - if (a == null && b != null) - return false; - if (a != null && b == null) - return false; - if (a == null && b == null) - return true; - if (a.equals(b)) - return true; - return false; - } -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domains.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domains.java deleted file mode 100644 index a8f2064b..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domains.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import java.util.ArrayList; -import java.util.List; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "domains") -public class Domains { - private List domains = new ArrayList(); - - public void setDomains(List domains) { - this.domains = domains; - } - - public List getDomains() { - return domains; - } - -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grant.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grant.java deleted file mode 100644 index 20c2d128..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grant.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "grant") -public class Grant { - private String grantid; - private String domainid; - private String userid; - private String roleid; - - public String getGrantid() { - return this.grantid; - } - - public void setGrantid(String id) { - this.grantid = id; - } - - public String getDomainid() { - return domainid; - } - - public void setDomainid(String id) { - this.domainid = id; - } - - public String getUserid() { - return userid; - } - - public void setUserid(String id) { - this.userid = id; - } - - public String getRoleid() { - return roleid; - } - - public void setRoleid(String id) { - this.roleid = id; - } - - @Override - public int hashCode() { - return this.getUserid().hashCode(); - } - - @Override - public boolean equals(Object obj) { - Grant other = (Grant) obj; - if (other == null) - return false; - if (compareValues(getDomainid(), other.getDomainid()) - && compareValues(getRoleid(), other.getRoleid()) - && compareValues(getUserid(), other.getUserid())) - return true; - return false; - } - - private boolean compareValues(Object a, Object b) { - if (a == null && b != null) - return false; - if (a != null && b == null) - return false; - if (a == null && b == null) - return true; - if (a.equals(b)) - return true; - return false; - } -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grants.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grants.java deleted file mode 100644 index ce0d9b85..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grants.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - - -import java.util.ArrayList; -import java.util.List; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "grants") -public class Grants { - private List grants = new ArrayList(); - - public void setGrants(List grants) { - this.grants = grants; - } - - public List getGrants() { - return grants; - } - -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/IDMError.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/IDMError.java deleted file mode 100644 index f44c43d9..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/IDMError.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import javax.ws.rs.core.Response; -import javax.xml.bind.annotation.XmlRootElement; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@XmlRootElement(name = "idmerror") -public class IDMError { - private static final Logger LOG = LoggerFactory.getLogger(IDMError.class); - - private String message; - private String details; - private int code = 500; - - public IDMError() { - }; - - public IDMError(int statusCode, String msg, String msgDetails) { - code = statusCode; - message = msg; - details = msgDetails; - } - - public String getMessage() { - return message; - } - - public void setMessage(String msg) { - this.message = msg; - } - - public String getDetails() { - return details; - } - - public void setDetails(String details) { - this.details = details; - } - - public Response response() { - LOG.error("error: {} details: {} status: {}", this.message, this.details, code); - return Response.status(this.code).entity(this).build(); - } - -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Role.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Role.java deleted file mode 100644 index de707496..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Role.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "role") -public class Role { - private String roleid; - private String name; - private String description; - private String domainid; - - public String getRoleid() { - return roleid; - } - - public void setRoleid(String id) { - this.roleid = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - @Override - public int hashCode() { - return this.name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - Role other = (Role) obj; - if (other == null) - return false; - if (compareValues(getName(), other.getName()) - && compareValues(getRoleid(), other.getRoleid()) - && compareValues(getDescription(), other.getDescription())) - return true; - return false; - } - - public void setDomainid(String domainid) { - this.domainid = domainid; - } - - public String getDomainid() { - return this.domainid; - } - - private boolean compareValues(Object a, Object b) { - if (a == null && b != null) - return false; - if (a != null && b == null) - return false; - if (a == null && b == null) - return true; - if (a.equals(b)) - return true; - return false; - } -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Roles.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Roles.java deleted file mode 100644 index 33521028..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Roles.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import java.util.ArrayList; -import java.util.List; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "roles") -public class Roles { - private List roles = new ArrayList(); - - public void setRoles(List roles) { - this.roles = roles; - } - - public List getRoles() { - return roles; - } - -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/User.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/User.java deleted file mode 100644 index c6c1f9a6..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/User.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "user") -public class User { - private String userid; - private String name; - private String description; - private Boolean enabled; - private String email; - private String password; - private String salt; - private String domainid; - - public String getUserid() { - return userid; - } - - public void setUserid(String id) { - this.userid = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Boolean isEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - this.enabled = enabled; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getEmail() { - return email; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getPassword() { - return password; - } - - public void setSalt(String s) { - this.salt = s; - } - - public String getSalt() { - return this.salt; - } - - public String getDomainid() { - return domainid; - } - - public void setDomainid(String domainid) { - this.domainid = domainid; - } - - @Override - public int hashCode() { - return this.name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - User other = (User) obj; - if (other == null) - return false; - if (compareValues(getName(), other.getName()) - && compareValues(getEmail(), other.getEmail()) - && compareValues(isEnabled(), other.isEnabled()) - && compareValues(getPassword(), other.getPassword()) - && compareValues(getSalt(), other.getSalt()) - && compareValues(getUserid(), other.getUserid()) - && compareValues(getDescription(), other.getDescription())) - return true; - return false; - } - - private boolean compareValues(Object a, Object b) { - if (a == null && b != null) - return false; - if (a != null && b == null) - return false; - if (a == null && b == null) - return true; - if (a.equals(b)) - return true; - return false; - } -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/UserPwd.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/UserPwd.java deleted file mode 100644 index 4750616d..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/UserPwd.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "userpwd") -public class UserPwd { - private String username; - private String userpwd; - - public String getUsername() { - return username; - } - - public void setUsername(String name) { - this.username = name; - } - - public String getUserpwd() { - return userpwd; - } - - public void setUserpwd(String pwd) { - this.userpwd = pwd; - } - -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Users.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Users.java deleted file mode 100644 index a0a001bd..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Users.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import java.util.ArrayList; -import java.util.List; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "users") -public class Users { - private List users = new ArrayList(); - - public void setUsers(List users) { - this.users = users; - } - - public List getUsers() { - return users; - } - -} diff --git a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Version.java b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Version.java deleted file mode 100644 index a88c1f80..00000000 --- a/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Version.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.api.model; - -/** - * - * @author peter.mellquist@hp.com - * - */ - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "version") -public class Version { - private String id; - private String updated; - private String status; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getUpdated() { - return updated; - } - - public void setUpdated(String name) { - this.updated = name; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - -} diff --git a/odl-aaa-moon/aaa-authn-basic/pom.xml b/odl-aaa-moon/aaa-authn-basic/pom.xml deleted file mode 100644 index f98e6294..00000000 --- a/odl-aaa-moon/aaa-authn-basic/pom.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn-basic - bundle - - - - org.opendaylight.aaa - aaa-authn - - - org.opendaylight.aaa - aaa-authn-api - - - org.slf4j - slf4j-api - - - com.sun.jersey - jersey-server - provided - - - org.osgi - org.osgi.core - provided - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - - junit - junit - test - - - org.slf4j - slf4j-simple - test - - - org.mockito - mockito-all - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.opendaylight.aaa.basic.Activator - - ${project.basedir}/META-INF - - - - - diff --git a/odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java b/odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java deleted file mode 100644 index bd57c9d3..00000000 --- a/odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.basic; - -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.opendaylight.aaa.api.CredentialAuth; -import org.opendaylight.aaa.api.TokenAuth; -import org.osgi.framework.BundleContext; - -public class Activator extends DependencyActivatorBase { - - @Override - public void init(BundleContext context, DependencyManager manager) throws Exception { - manager.add(createComponent() - .setInterface(new String[] { TokenAuth.class.getName() }, null) - .setImplementation(HttpBasicAuth.class) - .add(createServiceDependency().setService(CredentialAuth.class).setRequired(true))); - } - - @Override - public void destroy(BundleContext context, DependencyManager manager) throws Exception { - } - -} diff --git a/odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java b/odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java deleted file mode 100644 index eff47e63..00000000 --- a/odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.basic; - -import com.sun.jersey.core.util.Base64; -import java.util.List; -import java.util.Map; -import org.opendaylight.aaa.AuthenticationBuilder; -import org.opendaylight.aaa.PasswordCredentialBuilder; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.AuthenticationException; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.api.CredentialAuth; -import org.opendaylight.aaa.api.PasswordCredentials; -import org.opendaylight.aaa.api.TokenAuth; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An HTTP Basic authenticator. Note that this is provided as a Hydrogen - * backward compatible authenticator, but usage of this authenticator or HTTP - * Basic Authentication is highly discouraged due to its vulnerability. - * - * To obtain a token using the HttpBasicAuth Strategy, add a header to your HTTP - * request in the form: - * Authorization: Basic BASE_64_ENCODED_CREDENTIALS - * - * Where BASE_64_ENCODED_CREDENTIALS is the base 64 encoded value - * of the user's credentials in the following form: user:password - * - * For example, assuming the user is "admin" and the password is "admin": - * Authorization: Basic YWRtaW46YWRtaW4= - * - * @author liemmn - * - */ -public class HttpBasicAuth implements TokenAuth { - - public static final String AUTH_HEADER = "Authorization"; - - public static final String AUTH_SEP = ":"; - - public static final String BASIC_PREFIX = "Basic "; - - // TODO relocate this constant - public static final String DEFAULT_DOMAIN = "sdn"; - - /** - * username and password - */ - private static final int NUM_HEADER_CREDS = 2; - - /** - * username, password and domain - */ - private static final int NUM_TOKEN_CREDS = 3; - - private static final Logger LOG = LoggerFactory.getLogger(HttpBasicAuth.class); - - volatile CredentialAuth credentialAuth; - - private static boolean checkAuthHeaderFormat(final String authHeader) { - return (authHeader != null && authHeader.startsWith(BASIC_PREFIX)); - } - - private static String extractAuthHeader(final Map> headers) { - return headers.get(AUTH_HEADER).get(0); - } - - private static String[] extractCredentialArray(final String authHeader) { - return new String(Base64.base64Decode(authHeader.substring(BASIC_PREFIX.length()))) - .split(AUTH_SEP); - } - - private static boolean verifyCredentialArray(final String[] creds) { - return (creds != null && creds.length == NUM_HEADER_CREDS); - } - - private static String[] addDomainToCredentialArray(final String[] creds) { - String newCredentialArray[] = new String[NUM_TOKEN_CREDS]; - System.arraycopy(creds, 0, newCredentialArray, 0, creds.length); - newCredentialArray[2] = DEFAULT_DOMAIN; - return newCredentialArray; - } - - private static Authentication generateAuthentication( - CredentialAuth credentialAuth, final String[] creds) - throws ArrayIndexOutOfBoundsException { - final PasswordCredentials pc = new PasswordCredentialBuilder().setUserName(creds[0]) - .setPassword(creds[1]).setDomain(creds[2]).build(); - final Claim claim = credentialAuth.authenticate(pc); - return new AuthenticationBuilder(claim).build(); - } - - @Override - public Authentication validate(final Map> headers) - throws AuthenticationException { - if (headers.containsKey(AUTH_HEADER)) { - final String authHeader = extractAuthHeader(headers); - if (checkAuthHeaderFormat(authHeader)) { - // HTTP Basic Auth - String[] creds = extractCredentialArray(authHeader); - // If no domain was supplied then use the default one, which is - // "sdn". - if (verifyCredentialArray(creds)) { - creds = addDomainToCredentialArray(creds); - } - // Assumes correct formatting in form Base64("user:password"). - // Throws an exception if an unknown format is used. - try { - return generateAuthentication(this.credentialAuth, creds); - } catch (ArrayIndexOutOfBoundsException e) { - final String message = "Login Attempt in Bad Format." - + " Please provide user:password in Base64 format."; - LOG.info(message); - throw new AuthenticationException(message); - } - } - } - return null; - } - -} diff --git a/odl-aaa-moon/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java b/odl-aaa-moon/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java deleted file mode 100644 index 4ee439df..00000000 --- a/odl-aaa-moon/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.basic; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.sun.jersey.core.util.Base64; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.Before; -import org.junit.Test; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.PasswordCredentialBuilder; -import org.opendaylight.aaa.api.AuthenticationException; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.api.CredentialAuth; - -public class HttpBasicAuthTest { - private static final String USERNAME = "admin"; - private static final String PASSWORD = "admin"; - private static final String DOMAIN = "sdn"; - private HttpBasicAuth auth; - - @SuppressWarnings("unchecked") - @Before - public void setup() { - auth = new HttpBasicAuth(); - auth.credentialAuth = mock(CredentialAuth.class); - when( - auth.credentialAuth.authenticate(new PasswordCredentialBuilder() - .setUserName(USERNAME).setPassword(PASSWORD).setDomain(DOMAIN).build())) - .thenReturn( - new ClaimBuilder().setUser("admin").addRole("admin").setUserId("123") - .build()); - when( - auth.credentialAuth.authenticate(new PasswordCredentialBuilder() - .setUserName(USERNAME).setPassword("bozo").setDomain(DOMAIN).build())) - .thenThrow(new AuthenticationException("barf")); - } - - @Test - public void testValidateOk() throws UnsupportedEncodingException { - String data = USERNAME + ":" + PASSWORD + ":" + DOMAIN; - Map> headers = new HashMap<>(); - headers.put("Authorization", - Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8"))))); - Claim claim = auth.validate(headers); - assertNotNull(claim); - assertEquals(USERNAME, claim.user()); - assertEquals("admin", claim.roles().iterator().next()); - } - - @Test(expected = AuthenticationException.class) - public void testValidateBadPassword() throws UnsupportedEncodingException { - String data = USERNAME + ":bozo:" + DOMAIN; - Map> headers = new HashMap<>(); - headers.put("Authorization", - Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8"))))); - auth.validate(headers); - } - - @Test(expected = AuthenticationException.class) - public void testValidateBadPasswordNoDOMAIN() throws UnsupportedEncodingException { - String data = USERNAME + ":bozo"; - Map> headers = new HashMap<>(); - headers.put("Authorization", - Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8"))))); - auth.validate(headers); - } - - @Test(expected = AuthenticationException.class) - public void testBadHeaderFormatNoPassword() throws UnsupportedEncodingException { - // just provide the username - String data = USERNAME; - Map> headers = new HashMap<>(); - headers.put("Authorization", - Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8"))))); - auth.validate(headers); - } - - @Test(expected = AuthenticationException.class) - public void testBadHeaderFormat() throws UnsupportedEncodingException { - // provide username: - String data = USERNAME + "$" + PASSWORD; - Map> headers = new HashMap<>(); - headers.put("Authorization", - Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8"))))); - auth.validate(headers); - } -} diff --git a/odl-aaa-moon/aaa-authn-federation/pom.xml b/odl-aaa-moon/aaa-authn-federation/pom.xml deleted file mode 100644 index 0e84e185..00000000 --- a/odl-aaa-moon/aaa-authn-federation/pom.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn-federation - bundle - - - - org.opendaylight.aaa - aaa-authn-api - - - org.opendaylight.aaa - aaa-authn - - - org.slf4j - slf4j-api - - - com.sun.jersey - jersey-server - provided - - - javax.servlet - javax.servlet-api - provided - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - provided - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.common - provided - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.resourceserver - provided - - - org.osgi - org.osgi.core - provided - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - - com.sun.jersey.jersey-test-framework - jersey-test-framework-grizzly2 - test - - - org.eclipse.jetty - jetty-servlet-tester - test - - - junit - junit - test - - - org.mockito - mockito-all - test - - - org.slf4j - slf4j-simple - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - *,com.sun.jersey.spi.container.servlet - /oauth2/federation - federationConn - org.opendaylight.aaa.federation.Activator - ${project.basedir}/META-INF - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - package - - attach-artifact - - - - - ${project.build.directory}/classes/federation.cfg - cfg - config - - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/Activator.java b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/Activator.java deleted file mode 100644 index 4ae027c8..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/Activator.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.federation; - -import java.util.Dictionary; -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.opendaylight.aaa.api.ClaimAuth; -import org.opendaylight.aaa.api.IdMService; -import org.opendaylight.aaa.api.TokenStore; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.service.cm.ManagedService; - -/** - * An activator for the secure token server to inject in a - * CredentialAuth implementation. - * - * @author liemmn - * - */ -public class Activator extends DependencyActivatorBase { - private static final String FEDERATION_PID = "org.opendaylight.aaa.federation"; - - @Override - public void init(BundleContext context, DependencyManager manager) throws Exception { - manager.add(createComponent() - .setImplementation(ServiceLocator.getInstance()) - .add(createServiceDependency().setService(TokenStore.class).setRequired(true)) - .add(createServiceDependency().setService(IdMService.class).setRequired(true)) - .add(createServiceDependency().setService(ClaimAuth.class).setRequired(false) - .setCallbacks("claimAuthAdded", "claimAuthRemoved"))); - context.registerService(ManagedService.class, FederationConfiguration.instance(), - addPid(FederationConfiguration.defaults)); - } - - @Override - public void destroy(BundleContext context, DependencyManager manager) throws Exception { - } - - private Dictionary addPid(Dictionary dict) { - dict.put(Constants.SERVICE_PID, FEDERATION_PID); - return dict; - } -} diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ClaimAuthFilter.java b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ClaimAuthFilter.java deleted file mode 100644 index 10a1277d..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ClaimAuthFilter.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.federation; - -import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; -import static org.opendaylight.aaa.federation.FederationEndpoint.AUTH_CLAIM; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.api.ClaimAuth; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A generic {@link Filter} for {@link ClaimAuth} implementations. - *

- * This filter trusts any authentication metadata bound to a request. A request - * with fake authentication claims could be forged by an attacker and submitted - * to one of the Connector ports the engine is listening on and we would blindly - * accept the forged information in this filter. Therefore it is vital we only - * accept authentication claims from a trusted proxy. It is incumbent upon the - * site administrator to dedicate specific connector ports on which previously - * authenticated requests from a trusted proxy will be sent to and to assure - * only a trusted proxy can connect to that port. The site administrator must - * enumerate those ports in the configuration. We reject any request which did - * not originate on one of the configured secure proxy ports. - * - * @author liemmn - * - */ -public class ClaimAuthFilter implements Filter { - private static final Logger LOG = LoggerFactory.getLogger(ClaimAuthFilter.class); - - private static final String CGI_AUTH_TYPE = "AUTH_TYPE"; - private static final String CGI_PATH_INFO = "PATH_INFO"; - private static final String CGI_PATH_TRANSLATED = "PATH_TRANSLATED"; - private static final String CGI_QUERY_STRING = "QUERY_STRING"; - private static final String CGI_REMOTE_ADDR = "REMOTE_ADDR"; - private static final String CGI_REMOTE_HOST = "REMOTE_HOST"; - private static final String CGI_REMOTE_PORT = "REMOTE_PORT"; - private static final String CGI_REMOTE_USER = "REMOTE_USER"; - private static final String CGI_REMOTE_USER_GROUPS = "REMOTE_USER_GROUPS"; - private static final String CGI_REQUEST_METHOD = "REQUEST_METHOD"; - private static final String CGI_SCRIPT_NAME = "SCRIPT_NAME"; - private static final String CGI_SERVER_PROTOCOL = "SERVER_PROTOCOL"; - - static final String UNAUTHORIZED_PORT_ERR = "Unauthorized proxy port"; - - @Override - public void init(FilterConfig fc) throws ServletException { - } - - @Override - public void destroy() { - } - - @Override - public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) - throws IOException, ServletException { - Set secureProxyPorts; - int localPort; - - // Check to see if we are communicated over an authorized port or not - secureProxyPorts = FederationConfiguration.instance().secureProxyPorts(); - localPort = req.getLocalPort(); - if (!secureProxyPorts.contains(localPort)) { - ((HttpServletResponse) resp).sendError(SC_UNAUTHORIZED, UNAUTHORIZED_PORT_ERR); - return; - } - - // Let's do some transformation! - List claimAuthCollection = ServiceLocator.getInstance().getClaimAuthCollection(); - for (ClaimAuth ca : claimAuthCollection) { - Claim claim = ca.transform(claims((HttpServletRequest) req)); - if (claim != null) { - req.setAttribute(AUTH_CLAIM, claim); - // No need to do further transformation since it has been done - break; - } - } - chain.doFilter(req, resp); - } - - // Extract attributes and headers out of the request - private Map claims(HttpServletRequest req) { - String name; - Object objectValue; - String stringValue; - Map claims = new HashMap<>(); - - /* - * Tomcat has a bug/feature, not all attributes are enumerated by - * getAttributeNames() therefore getAttributeNames() cannot be used to - * obtain the full set of attributes. However if you know the name of - * the attribute a priori you can call getAttribute() and obtain the - * value. Therefore we maintain a list of attribute names - * (httpAttributes) which will be used to call getAttribute() with so we - * don't miss essential attributes. - * - * This is the Tomcat bug, note it is marked WONTFIX. Bug 25363 - - * request.getAttributeNames() not working properly Status: RESOLVED - * WONTFIX https://issues.apache.org/bugzilla/show_bug.cgi?id=25363 - * - * The solution adopted by Tomcat is to document the behavior in the - * "The Apache Tomcat Connector - Reference Guide" under the JkEnvVar - * property where is says: - * - * You can retrieve the variables on Tomcat as request attributes via - * request.getAttribute(attributeName). Note that the variables send via - * JkEnvVar will not be listed in request.getAttributeNames(). - */ - - // Capture attributes which can be enumerated ... - @SuppressWarnings("unchecked") - Enumeration attrs = req.getAttributeNames(); - while (attrs.hasMoreElements()) { - name = attrs.nextElement(); - objectValue = req.getAttribute(name); - if (objectValue instanceof String) { - // metadata might be i18n, assume UTF8 and decode - stringValue = decodeUTF8((String) objectValue); - objectValue = stringValue; - } - claims.put(name, objectValue); - } - - // Capture specific attributes which cannot be enumerated ... - for (String attr : FederationConfiguration.instance().httpAttributes()) { - name = attr; - objectValue = req.getAttribute(name); - if (objectValue instanceof String) { - // metadata might be i18n, assume UTF8 and decode - stringValue = decodeUTF8((String) objectValue); - objectValue = stringValue; - } - claims.put(name, objectValue); - } - - /* - * In general we should not utilize HTTP headers as validated security - * assertions because they are too easy to forge. Therefore in general - * we don't include HTTP headers, however in certain circumstances - * specific headers may be acceptable, thus we permit an admin to - * configure the capture of specific headers. - */ - for (String header : FederationConfiguration.instance().httpHeaders()) { - claims.put(header, req.getHeader(header)); - } - - // Capture standard CGI variables... - claims.put(CGI_AUTH_TYPE, req.getAuthType()); - claims.put(CGI_PATH_INFO, req.getPathInfo()); - claims.put(CGI_PATH_TRANSLATED, req.getPathTranslated()); - claims.put(CGI_QUERY_STRING, req.getQueryString()); - claims.put(CGI_REMOTE_ADDR, req.getRemoteAddr()); - claims.put(CGI_REMOTE_HOST, req.getRemoteHost()); - claims.put(CGI_REMOTE_PORT, req.getRemotePort()); - // remote user might be i18n, assume UTF8 and decode - claims.put(CGI_REMOTE_USER, decodeUTF8(req.getRemoteUser())); - claims.put(CGI_REMOTE_USER_GROUPS, req.getAttribute(CGI_REMOTE_USER_GROUPS)); - claims.put(CGI_REQUEST_METHOD, req.getMethod()); - claims.put(CGI_SCRIPT_NAME, req.getServletPath()); - claims.put(CGI_SERVER_PROTOCOL, req.getProtocol()); - - if (LOG.isDebugEnabled()) { - LOG.debug("ClaimAuthFilter claims = {}", claims.toString()); - } - - return claims; - } - - /** - * Decode from UTF-8, return Unicode. - * - * If we're unable to UTF-8 decode the string the fallback is to return the - * string unmodified and log a warning. - * - * Some data, especially metadata attached to a user principal may be - * internationalized (i18n). The classic examples are the user's name, - * location, organization, etc. We need to be able to read this metadata and - * decode it into unicode characters so that we properly handle i18n string - * values. - * - * One of the the prolems is we often don't know the encoding (i.e. charset) - * of the string. RFC-5987 is supposed to define how non-ASCII values are - * transmitted in HTTP headers, this is a follow on from the work in - * RFC-2231. However at the time of this writing these RFC's are not - * implemented in the Servlet Request classes. Not only are these RFC's - * unimplemented but they are specific to HTTP headers, much of our metadata - * arrives via attributes as opposed to being in a header. - * - * Note: ASCII encoding is a subset of UTF-8 encoding therefore any strings - * which are pure ASCII will decode from UTF-8 just fine. However on the - * other hand Latin-1 (ISO-8859-1) encoding is not compatible with UTF-8 for - * code points in the range 128-255 (i.e. beyond 7-bit ascii). ISO-8859-1 is - * the default encoding for HTTP and HTML 4, however the consensus is the - * use of ISO-8859-1 was a mistake and Unicode with UTF-8 encoding is now - * the norm. If a string value is transmitted encoded in ISO-8859-1 - * contaiing code points in the range 128-255 and we try to UTF-8 decode it - * it will either not be the correct decoded string or it will throw a - * decoding exception. - * - * Conventional practice at the moment is for the sending side to encode - * internationalized values in UTF-8 with the receving end decoding the - * value back from UTF-8. We do not expect the use of ISO-8859-1 on these - * attributes. However due to peculiarities of the Java String - * implementation we have to specify the raw bytes are encoded in ISO-8859-1 - * just to get back the raw bytes to be able to feed into the UTF-8 decoder. - * This doesn't seem right but it is because we need the full 8-bit byte and - * the only way to say "unmodified 8-bit bytes" in Java is to call it - * ISO-8859-1. Ugh! - * - * @param string - * The input string in UTF-8 to be decoded. - * @return Unicode string - */ - private String decodeUTF8(String string) { - if (string == null) { - return null; - } - try { - return new String(string.getBytes("ISO8859-1"), "UTF-8"); - } catch (UnsupportedEncodingException e) { - LOG.warn("Unable to UTF-8 decode: ", string, e); - return string; - } - } - -} diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationConfiguration.java b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationConfiguration.java deleted file mode 100644 index a68dc15c..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationConfiguration.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.federation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedService; - -/** - * AAA federation configurations in OSGi. - * - * @author liemmn - * - */ -public class FederationConfiguration implements ManagedService { - private static final String FEDERATION_CONFIG_ERR = "Error saving federation configuration"; - - static final String HTTP_HEADERS = "httpHeaders"; - static final String HTTP_ATTRIBUTES = "httpAttributes"; - static final String SECURE_PROXY_PORTS = "secureProxyPorts"; - - static FederationConfiguration instance = new FederationConfiguration(); - - static final Hashtable defaults = new Hashtable<>(); - static { - defaults.put(HTTP_HEADERS, ""); - defaults.put(HTTP_ATTRIBUTES, ""); - } - private static Map configs = new ConcurrentHashMap<>(); - - // singleton - private FederationConfiguration() { - } - - public static FederationConfiguration instance() { - return instance; - } - - @Override - public void updated(Dictionary props) throws ConfigurationException { - if (props == null) { - configs.clear(); - configs.putAll(defaults); - } else { - try { - Enumeration keys = props.keys(); - while (keys.hasMoreElements()) { - String key = keys.nextElement(); - configs.put(key, (String) props.get(key)); - } - } catch (Throwable t) { - throw new ConfigurationException(null, FEDERATION_CONFIG_ERR, t); - } - } - } - - public List httpHeaders() { - String headers = configs.get(HTTP_HEADERS); - return (headers == null) ? new ArrayList() : Arrays.asList(headers.split(" ")); - } - - public List httpAttributes() { - String attributes = configs.get(HTTP_ATTRIBUTES); - return (attributes == null) ? new ArrayList() : Arrays - .asList(attributes.split(" ")); - } - - public Set secureProxyPorts() { - String ports = configs.get(SECURE_PROXY_PORTS); - Set secureProxyPorts = new TreeSet(); - - if (ports != null && !ports.isEmpty()) { - for (String port : ports.split(" ")) { - secureProxyPorts.add(Integer.parseInt(port)); - } - } - return secureProxyPorts; - } - -} diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationEndpoint.java b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationEndpoint.java deleted file mode 100644 index 6ac76c0a..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationEndpoint.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.federation; - -import static javax.servlet.http.HttpServletResponse.SC_CREATED; -import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.List; -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; -import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; -import org.apache.oltu.oauth2.as.issuer.UUIDValueGenerator; -import org.apache.oltu.oauth2.as.response.OAuthASResponse; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.OAuthResponse; -import org.opendaylight.aaa.AuthenticationBuilder; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.AuthenticationException; -import org.opendaylight.aaa.api.Claim; - -/** - * An endpoint for claim-based authentication federation (in-bound). - * - * @author liemmn - * - */ -public class FederationEndpoint extends HttpServlet { - - private static final long serialVersionUID = -5553885846238987245L; - - /** An in-bound authentication claim */ - static final String AUTH_CLAIM = "AAA-CLAIM"; - - private static final String UNAUTHORIZED = "unauthorized"; - - private transient OAuthIssuer oi; - - @Override - public void init(ServletConfig config) throws ServletException { - oi = new OAuthIssuerImpl(new UUIDValueGenerator()); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, - ServletException { - try { - createRefreshToken(req, resp); - } catch (Exception e) { - error(resp, SC_UNAUTHORIZED, e.getMessage()); - } - } - - // Create a refresh token - private void createRefreshToken(HttpServletRequest req, HttpServletResponse resp) - throws OAuthSystemException, IOException { - Claim claim = (Claim) req.getAttribute(AUTH_CLAIM); - oauthRefreshTokenResponse(resp, claim); - } - - // Build OAuth refresh token response from the given claim mapped and - // injected by the external IdP - private void oauthRefreshTokenResponse(HttpServletResponse resp, Claim claim) - throws OAuthSystemException, IOException { - if (claim == null) { - throw new AuthenticationException(UNAUTHORIZED); - } - - String userName = claim.user(); - // Need to have at least a mapped username! - if (userName == null) { - throw new AuthenticationException(UNAUTHORIZED); - } - - String domain = claim.domain(); - // Need to have at least a domain! - if (domain == null) { - throw new AuthenticationException(UNAUTHORIZED); - } - - String userId = userName + "@" + domain; - - // Create an unscoped ODL context from the external claim - Authentication auth = new AuthenticationBuilder(new ClaimBuilder(claim).setUserId(userId) - .build()).setExpiration(tokenExpiration()).build(); - - // Create OAuth response - String token = oi.refreshToken(); - OAuthResponse r = OAuthASResponse - .tokenResponse(SC_CREATED) - .setRefreshToken(token) - .setExpiresIn(Long.toString(auth.expiration())) - .setScope( - // Use mapped domain if there is one, else list - // all the ones that this user has access to - (claim.domain().isEmpty()) ? listToString(ServiceLocator.getInstance() - .getIdmService().listDomains(userId)) : claim.domain()) - .buildJSONMessage(); - // Cache this token... - ServiceLocator.getInstance().getTokenStore().put(token, auth); - write(resp, r); - } - - // Token expiration - private long tokenExpiration() { - return ServiceLocator.getInstance().getTokenStore().tokenExpiration(); - } - - // Space-delimited string from a list of strings - private String listToString(List list) { - StringBuffer sb = new StringBuffer(); - for (String s : list) { - sb.append(s).append(" "); - } - return sb.toString().trim(); - } - - // Emit an error OAuthResponse with the given HTTP code - private void error(HttpServletResponse resp, int httpCode, String error) { - try { - OAuthResponse r = OAuthResponse.errorResponse(httpCode).setError(error) - .buildJSONMessage(); - write(resp, r); - } catch (Exception e1) { - // Nothing to do here - } - } - - // Write out an OAuthResponse - private void write(HttpServletResponse resp, OAuthResponse r) throws IOException { - resp.setStatus(r.getResponseStatus()); - PrintWriter pw = resp.getWriter(); - pw.print(r.getBody()); - pw.flush(); - pw.close(); - } -} diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ServiceLocator.java b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ServiceLocator.java deleted file mode 100644 index dd861514..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ServiceLocator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.federation; - -import java.util.List; -import java.util.Vector; -import org.opendaylight.aaa.api.ClaimAuth; -import org.opendaylight.aaa.api.IdMService; -import org.opendaylight.aaa.api.TokenStore; - -/** - * A service locator to bridge between the web world and OSGi world. - * - * @author liemmn - * - */ -public class ServiceLocator { - - private static final ServiceLocator instance = new ServiceLocator(); - - protected volatile List claimAuthCollection = new Vector<>(); - - protected volatile TokenStore tokenStore; - - protected volatile IdMService idmService; - - private ServiceLocator() { - } - - public static ServiceLocator getInstance() { - return instance; - } - - /** - * Called through reflection from the federation Activator - * - * @see org.opendaylight.aaa.federation.ServiceLocator - * @param ca the injected claims implementation - */ - protected void claimAuthAdded(ClaimAuth ca) { - this.claimAuthCollection.add(ca); - } - - /** - * Called through reflection from the federation Activator - * - * @see org.opendaylight.aaa.federation.Activator - * @param ca the claims implementation to remove - */ - protected void claimAuthRemoved(ClaimAuth ca) { - this.claimAuthCollection.remove(ca); - } - - public List getClaimAuthCollection() { - return claimAuthCollection; - } - - public void setClaimAuthCollection(List claimAuthCollection) { - this.claimAuthCollection = claimAuthCollection; - } - - public TokenStore getTokenStore() { - return tokenStore; - } - - public void setTokenStore(TokenStore tokenStore) { - this.tokenStore = tokenStore; - } - - public IdMService getIdmService() { - return idmService; - } - - public void setIdmService(IdMService idmService) { - this.idmService = idmService; - } -} diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/SssdFilter.java b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/SssdFilter.java deleted file mode 100644 index 9223c6dd..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/SssdFilter.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Red Hat, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.federation; - -import java.io.IOException; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; - -class SssdHeadersRequest extends HttpServletRequestWrapper { - private static final String headerPrefix = "X-SSSD-"; - - public SssdHeadersRequest(HttpServletRequest request) { - super(request); - } - - public Object getAttribute(String name) { - HttpServletRequest request = (HttpServletRequest) getRequest(); - String headerValue; - - headerValue = request.getHeader(headerPrefix + name); - if (headerValue != null) { - return headerValue; - } else { - return request.getAttribute(name); - } - } - - @Override - public String getRemoteUser() { - HttpServletRequest request = (HttpServletRequest) getRequest(); - String headerValue; - - headerValue = request.getHeader(headerPrefix + "REMOTE_USER"); - if (headerValue != null) { - return headerValue; - } else { - return request.getRemoteUser(); - } - } - - @Override - public String getAuthType() { - HttpServletRequest request = (HttpServletRequest) getRequest(); - String headerValue; - - headerValue = request.getHeader(headerPrefix + "AUTH_TYPE"); - if (headerValue != null) { - return headerValue; - } else { - return request.getAuthType(); - } - } - - @Override - public String getRemoteAddr() { - HttpServletRequest request = (HttpServletRequest) getRequest(); - String headerValue; - - headerValue = request.getHeader(headerPrefix + "REMOTE_ADDR"); - if (headerValue != null) { - return headerValue; - } else { - return request.getRemoteAddr(); - } - } - - @Override - public String getRemoteHost() { - HttpServletRequest request = (HttpServletRequest) getRequest(); - String headerValue; - - headerValue = request.getHeader(headerPrefix + "REMOTE_HOST"); - if (headerValue != null) { - return headerValue; - } else { - return request.getRemoteHost(); - } - } - - @Override - public int getRemotePort() { - HttpServletRequest request = (HttpServletRequest) getRequest(); - String headerValue; - - headerValue = request.getHeader(headerPrefix + "REMOTE_PORT"); - if (headerValue != null) { - return Integer.parseInt(headerValue); - } else { - return request.getRemotePort(); - } - } - -} - -/** - * Populate HttpRequestServlet API data from HTTP extension headers. - * - * When SSSD is used for authentication and identity lookup those actions occur - * in an Apache HTTP server which is fronting the servlet container. After - * successful authentication Apache will proxy the request to the container - * along with additional authentication and identity metadata. - * - * The preferred way to transport the metadata and have it appear seamlessly in - * the servlet API is via the AJP protocol. However AJP may not be available or - * desirable. An alternative method is to transport the metadata in extension - * HTTP headers. However we still want the standard servlet request API methods - * to work. Another way to say this is we do not want upper layers to be aware - * of the transport mechanism. To achieve this we wrap the HttpServletRequest - * class and override specific methods which need to extract the data from the - * extension HTTP headers. (This is roughly equivalent to what happens when AJP - * is implemented natively in the container). - * - * The extension HTTP headers are identified by the prefix "X-SSSD-". The - * overridden methods check for the existence of the appropriate extension - * header and if present returns the value found in the extension header, - * otherwise it returns the value from the method it's wrapping. - * - */ -public class SssdFilter implements Filter { - @Override - public void init(FilterConfig fc) throws ServletException { - } - - @Override - public void destroy() { - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, - FilterChain filterChain) throws IOException, ServletException { - if (servletRequest instanceof HttpServletRequest) { - HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; - SssdHeadersRequest request = new SssdHeadersRequest(httpServletRequest); - filterChain.doFilter(request, servletResponse); - } else { - filterChain.doFilter(servletRequest, servletResponse); - } - } -} diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.properties b/odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.properties deleted file mode 100644 index 4323c04d..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.properties +++ /dev/null @@ -1,11 +0,0 @@ -org.opendaylight.aaa.federation.name = Opendaylight AAA Federation Configuration -org.opendaylight.aaa.federation.description = Configuration for AAA federation -org.opendaylight.aaa.federation.httpHeaders.name = Custom HTTP Headers -org.opendaylight.aaa.federation.httpHeaders.description = Space-delimited list of \ -specific HTTP headers to capture for authentication federation. -org.opendaylight.aaa.federation.httpAttributes.name = Custom HTTP Attributes -org.opendaylight.aaa.federation.httpAttributes.description = Space-delimited list of \ -specific HTTP attributes to capture for authentication federation. -org.opendaylight.aaa.federation.secureProxyPorts.name = Secure Proxy Ports -org.opendaylight.aaa.federation.secureProxyPorts.description = Space-delimited list of \ -port numbers on which a trusted HTTP proxy performing authentication forwards pre-authenticated requests. diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.xml b/odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.xml deleted file mode 100644 index e2efd3d4..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/resources/WEB-INF/web.xml b/odl-aaa-moon/aaa-authn-federation/src/main/resources/WEB-INF/web.xml deleted file mode 100644 index 9fd9751f..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/resources/WEB-INF/web.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - federation - org.opendaylight.aaa.federation.FederationEndpoint - 1 - - - federation - /* - - - - - SssdFilter - org.opendaylight.aaa.federation.SssdFilter - - - SssdFilter - /* - - - ClaimAuthFilter - org.opendaylight.aaa.federation.ClaimAuthFilter - - - ClaimAuthFilter - /* - - - diff --git a/odl-aaa-moon/aaa-authn-federation/src/main/resources/federation.cfg b/odl-aaa-moon/aaa-authn-federation/src/main/resources/federation.cfg deleted file mode 100644 index 60ef1c46..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/main/resources/federation.cfg +++ /dev/null @@ -1,3 +0,0 @@ -httpHeaders= -httpAttributes= -secureProxyPorts= diff --git a/odl-aaa-moon/aaa-authn-federation/src/test/java/org/opendaylight/aaa/federation/FederationEndpointTest.java b/odl-aaa-moon/aaa-authn-federation/src/test/java/org/opendaylight/aaa/federation/FederationEndpointTest.java deleted file mode 100644 index ae098652..00000000 --- a/odl-aaa-moon/aaa-authn-federation/src/test/java/org/opendaylight/aaa/federation/FederationEndpointTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.federation; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyMap; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.TreeSet; -import org.eclipse.jetty.testing.HttpTester; -import org.eclipse.jetty.testing.ServletTester; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.api.ClaimAuth; -import org.opendaylight.aaa.api.IdMService; -import org.opendaylight.aaa.api.TokenStore; - -/** - * A unit test for federation endpoint. - * - * @author liemmn - * - */ -public class FederationEndpointTest { - private static final long TOKEN_TIMEOUT_SECS = 10; - private static final String CONTEXT = "/oauth2/federation"; - - private final static ServletTester server = new ServletTester(); - private static final Claim claim = new ClaimBuilder().setUser("bob").setUserId("1234") - .addRole("admin").build(); - - @BeforeClass - public static void init() throws Exception { - // Set up server - server.setContextPath(CONTEXT); - - // Add our servlet under test - server.addServlet(FederationEndpoint.class, "/*"); - - // Add ClaimAuth filter - server.addFilter(ClaimAuthFilter.class, "/*", 0); - - // Let's do dis - server.start(); - } - - @AfterClass - public static void shutdown() throws Exception { - server.stop(); - } - - @Before - public void setup() { - mockServiceLocator(); - when(ServiceLocator.getInstance().getTokenStore().tokenExpiration()).thenReturn( - TOKEN_TIMEOUT_SECS); - } - - @After - public void teardown() { - ServiceLocator.getInstance().getClaimAuthCollection().clear(); - } - - @Test - public void testFederationUnconfiguredProxyPort() throws Exception { - HttpTester req = new HttpTester(); - req.setMethod("POST"); - req.setURI(CONTEXT + "/"); - req.setVersion("HTTP/1.0"); - - HttpTester resp = new HttpTester(); - resp.parse(server.getResponses(req.generate())); - assertEquals(401, resp.getStatus()); - } - - @Test - @SuppressWarnings("unchecked") - public void testFederation() throws Exception { - when(ServiceLocator.getInstance().getClaimAuthCollection().get(0).transform(anyMap())) - .thenReturn(claim); - when(ServiceLocator.getInstance().getIdmService().listDomains(anyString())).thenReturn( - Arrays.asList("pepsi", "coke")); - - // Configure secure port (of zero) - FederationConfiguration.instance = mock(FederationConfiguration.class); - when(FederationConfiguration.instance.secureProxyPorts()).thenReturn( - new TreeSet(Arrays.asList(0))); - - HttpTester req = new HttpTester(); - req.setMethod("POST"); - req.setURI(CONTEXT + "/"); - req.setVersion("HTTP/1.0"); - - HttpTester resp = new HttpTester(); - resp.parse(server.getResponses(req.generate())); - assertEquals(201, resp.getStatus()); - String content = resp.getContent(); - assertTrue(content.contains("pepsi coke")); - } - - private static void mockServiceLocator() { - ServiceLocator.getInstance().setIdmService(mock(IdMService.class)); - ServiceLocator.getInstance().setTokenStore(mock(TokenStore.class)); - ServiceLocator.getInstance().getClaimAuthCollection().add(mock(ClaimAuth.class)); - } -} diff --git a/odl-aaa-moon/aaa-authn-keystone/pom.xml b/odl-aaa-moon/aaa-authn-keystone/pom.xml deleted file mode 100644 index ee1d3278..00000000 --- a/odl-aaa-moon/aaa-authn-keystone/pom.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn-keystone - bundle - - - - org.opendaylight.aaa - aaa-authn - - - org.opendaylight.aaa - aaa-authn-api - - - org.slf4j - slf4j-api - - - com.sun.jersey - jersey-server - provided - - - javax.servlet - javax.servlet-api - provided - - - org.osgi - org.osgi.core - provided - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - com.fasterxml.jackson.core - jackson-annotations - provided - - - com.fasterxml.jackson.core - jackson-core - provided - - - com.fasterxml.jackson.core - jackson-databind - provided - - - org.apache.httpcomponents - httpcore-osgi - ${httpclient.version} - - - org.apache.httpcomponents - httpclient-osgi - ${httpclient.version} - - - - com.sun.jersey.jersey-test-framework - jersey-test-framework-grizzly2 - test - - - junit - junit - test - - - org.slf4j - slf4j-simple - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.opendaylight.aaa.keystone.Activator - - ${project.basedir}/META-INF - - - - - diff --git a/odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/Activator.java b/odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/Activator.java deleted file mode 100644 index c3c3bfb1..00000000 --- a/odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/Activator.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.keystone; - -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.opendaylight.aaa.api.TokenAuth; -import org.osgi.framework.BundleContext; - -/** - * An activator for {@link KeystoneTokenAuth}. - * - * @author liemmn - * - */ -public class Activator extends DependencyActivatorBase { - - @Override - public void init(BundleContext context, DependencyManager manager) throws Exception { - manager.add(createComponent().setInterface(new String[] { TokenAuth.class.getName() }, null) - .setImplementation(KeystoneTokenAuth.class)); - } - - @Override - public void destroy(BundleContext context, DependencyManager manager) throws Exception { - } - -} diff --git a/odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/KeystoneTokenAuth.java b/odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/KeystoneTokenAuth.java deleted file mode 100644 index 6f4b4bb1..00000000 --- a/odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/KeystoneTokenAuth.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.keystone; - -import java.util.List; -import java.util.Map; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.TokenAuth; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A Keystone {@link TokenAuth} filter. - * - * @author liemmn - */ -public class KeystoneTokenAuth implements TokenAuth { - private static final Logger LOG = LoggerFactory.getLogger(KeystoneTokenAuth.class); - - static final String TOKEN = "X-Auth-Token"; - - @Override - public Authentication validate(Map> headers) { - if (!headers.containsKey(TOKEN)) { - return null; // Not a Keystone token - } - - // TODO: Call into Keystone to get security context... - LOG.info("Not yet validating token {}", headers.get(TOKEN).get(0)); - return null; - } - -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/pom.xml b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/pom.xml deleted file mode 100644 index fede7e5e..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/pom.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - 4.0.0 - - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../../parent - - - aaa-authn-mdsal-api - - - - org.opendaylight.aaa - aaa-authn-api - - - org.opendaylight.mdsal - yang-binding - - - org.opendaylight.mdsal.model - ietf-inet-types - - - org.opendaylight.mdsal.model - ietf-yang-types - - - org.opendaylight.mdsal.model - yang-ext - - - - - - - org.apache.felix - maven-bundle-plugin - ${bundle.plugin.version} - true - - - org.apache.maven.plugins - maven-javadoc-plugin - - maven - - - - - aggregate - - site - - - - - org.opendaylight.yangtools - yang-maven-plugin - ${yangtools.version} - - - - generate-sources - - - src/main/yang - - - - org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl - - ${salGeneratorPath} - - - true - - - - - - - org.opendaylight.mdsal - maven-sal-api-gen-plugin - ${yangtools.version} - jar - - - - - - bundle - - diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/src/main/yang/aaa-authn-model.yang b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/src/main/yang/aaa-authn-model.yang deleted file mode 100644 index 227cb313..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/src/main/yang/aaa-authn-model.yang +++ /dev/null @@ -1,154 +0,0 @@ -module aaa-authn-model { - yang-version 1; - namespace "urn:aaa:yang:authn:claims"; - prefix "authn"; - organization "TBD"; - - contact "wdec@cisco.com"; - - revision 2014-10-29 { - description - "Initial revision."; - } - -//Main module begins - -// Following container provides the AuthN Claims data-structure - - container tokencache { - config false; - list claims { - key "token"; - - leaf token { - type string; - description "Token"; - } - leaf clientId { - type string; - description "id of the authorized client, or null if anonymous"; - } - leaf userId { - type string; - description "Unique user-id. User IDs are system-created"; - } - leaf user { - type string; - description "User name"; - } - leaf domain { - type string; - description "Fully-qualified domain name"; - } - leaf-list roles { - type string; - description "Assigned user roles"; - } - } - } - - container token_cache_times { - - list token_list { - key userId; - - leaf userId { - //TODO: Change to instance-ref - type string; - } - - list user_tokens { - key tokenid; - leaf tokenid { - type leafref {path "/tokencache/claims/token";} - } - leaf timestamp { - type uint64; - } - leaf expiration { - type int64; - description "Expiration milliseconds since start of UTC epoch"; - } - } - } - } - - //authentication model is for generating objects to be stores in the - //data store for all the prev idm model objects. - container authentication{ - list domain{ - key domainid; - leaf domainid { - type string; - } - leaf name { - type string; - } - leaf description { - type string; - } - leaf enabled { - type boolean; - } - } - - list user { - key userid; - leaf userid { - type string; - } - leaf name { - type string; - } - leaf description { - type string; - } - leaf enabled { - type boolean; - } - leaf email { - type string; - } - leaf password { - type string; - } - leaf salt { - type string; - } - leaf domainid { - type string; - } - } - list role { - key roleid; - leaf roleid { - type string; - } - leaf name { - type string; - } - leaf description { - type string; - } - leaf domainid { - type string; - } - } - - list grant { - key grantid; - leaf grantid { - type string; - } - leaf domainid { - type string; - } - leaf userid { - type string; - } - leaf roleid { - type string; - } - } - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/pom.xml b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/pom.xml deleted file mode 100644 index f01969a4..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/pom.xml +++ /dev/null @@ -1,40 +0,0 @@ - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../../parent - - - aaa-authn-mdsal-config - AuthN Token Store Service Configuration file - jar - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - - attach-artifact - - package - - - - ${project.build.directory}/classes/initial/${config.authn.store.configfile} - xml - config - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/src/main/resources/initial/08-authn-config.xml b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/src/main/resources/initial/08-authn-config.xml deleted file mode 100644 index e4a78f4d..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/src/main/resources/initial/08-authn-config.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - authn:aaa-authn-mdsal-store - aaa-authn-mdsal-store - - - dom:dom-broker-osgi-registry - - dom-broker - - - - binding:binding-async-data-broker - - binding-data-broker - - 3600000 - 15 - CHANGE_ME - - - - - - - config:aaa:authn:mdsal:store?module=aaa-authn-mdsal-store-cfg&revision=2014-10-31 - - - diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/pom.xml b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/pom.xml deleted file mode 100644 index c36febee..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/pom.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../../parent - - - aaa-authn-mdsal-store-impl - bundle - - - 1.5.2 - - - - - org.opendaylight.controller - sal-binding-util - - - org.opendaylight.controller - sal-common-util - - - org.opendaylight.yangtools - yang-data-api - - - commons-codec - commons-codec - - - org.opendaylight.controller - sal-binding-api - - - org.opendaylight.controller - config-api - - - org.opendaylight.controller - sal-binding-config - - - org.opendaylight.aaa - aaa-authn-api - - - org.opendaylight.aaa - aaa-authn - - - org.opendaylight.controller - sal-core-api - - - org.opendaylight.aaa - aaa-authn-mdsal-api - - - - - junit - junit - test - - - org.mockito - mockito-all - test - - - org.slf4j - slf4j-simple - test - - - org.powermock - powermock-api-mockito - ${powermock.version} - test - - - org.powermock - powermock-module-junit4 - ${powermock.version} - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - - org.opendaylight.yang.gen.v1.config.aaa.authn.mdsal.store.* - - - - - - - org.opendaylight.yangtools - yang-maven-plugin - ${yangtools.version} - - - config - - generate-sources - - - - - - org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator - - ${jmxGeneratorPath} - - - urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang - - - - - org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl - ${salGeneratorPath} - - - true - - - - - - org.opendaylight.controller - yang-jmx-generator-plugin - ${config.version} - - - org.opendaylight.mdsal - maven-sal-api-gen-plugin - ${yangtools.version} - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/AuthNStore.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/AuthNStore.java deleted file mode 100644 index 09170182..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/AuthNStore.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store; - -import com.google.common.base.Optional; -import com.google.common.util.concurrent.CheckedFuture; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import java.math.BigInteger; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.TokenStore; -import org.opendaylight.aaa.authn.mdsal.store.util.AuthNStoreUtil; -import org.opendaylight.controller.md.sal.binding.api.DataBroker; -import org.opendaylight.controller.md.sal.binding.api.ReadTransaction; -import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; -import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.TokenCacheTimes; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.TokenList; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.TokenListKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.token_list.UserTokens; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.tokencache.Claims; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class AuthNStore implements AutoCloseable, TokenStore { - - private static final Logger LOG = LoggerFactory.getLogger(AuthNStore.class); - private DataBroker broker; - private static BigInteger timeToLive; - private static Integer timeToWait; - private final ExecutorService deleteExpiredTokenThread = Executors.newFixedThreadPool(1); - private final DataEncrypter dataEncrypter; - - public AuthNStore(final DataBroker dataBroker, final String config_key) { - this.broker = dataBroker; - this.dataEncrypter = new DataEncrypter(config_key); - LOG.info("Created MD-SAL AAA Token Cache Service..."); - } - - @Override - public void close() throws Exception { - deleteExpiredTokenThread.shutdown(); - LOG.info("MD-SAL AAA Token Cache closed..."); - - } - - @Override - public void put(String token, Authentication auth) { - token = dataEncrypter.encrypt(token); - Claims claims = AuthNStoreUtil.createClaimsRecord(token, auth); - - // create and insert parallel struct - UserTokens userTokens = AuthNStoreUtil.createUserTokens(token, timeToLive.longValue()); - TokenList tokenlist = AuthNStoreUtil.createTokenList(userTokens, auth.userId()); - - writeClaimAndTokenToStore(claims, userTokens, tokenlist); - deleteExpiredTokenThread.execute(deleteOldTokens(claims)); - } - - @Override - public Authentication get(String token) { - token = dataEncrypter.encrypt(token); - Authentication authentication = null; - Claims claims = readClaims(token); - if (claims != null) { - UserTokens userToken = readUserTokensFromDS(claims.getToken(), claims.getUserId()); - authentication = AuthNStoreUtil.convertClaimToAuthentication(claims, - userToken.getExpiration()); - } - deleteExpiredTokenThread.execute(deleteOldTokens(claims)); - return authentication; - } - - @Override - public boolean delete(String token) { - token = dataEncrypter.encrypt(token); - boolean result = false; - Claims claims = readClaims(token); - result = deleteClaims(token); - if (result) { - deleteUserTokenFromDS(token, claims.getUserId()); - } - deleteExpiredTokenThread.execute(deleteOldTokens(claims)); - return result; - } - - @Override - public long tokenExpiration() { - return timeToLive.longValue(); - } - - public void setTimeToLive(BigInteger timeToLive) { - this.timeToLive = timeToLive; - } - - public void setTimeToWait(Integer timeToWait) { - this.timeToWait = timeToWait; - } - - private void writeClaimAndTokenToStore(final Claims claims, UserTokens usertokens, - final TokenList tokenlist) { - - final InstanceIdentifier claims_iid = AuthNStoreUtil.createInstIdentifierForTokencache(claims.getToken()); - WriteTransaction tx = broker.newWriteOnlyTransaction(); - tx.put(LogicalDatastoreType.OPERATIONAL, claims_iid, claims, true); - - final InstanceIdentifier userTokens_iid = AuthNStoreUtil.createInstIdentifierUserTokens( - tokenlist.getUserId(), usertokens.getTokenid()); - tx.put(LogicalDatastoreType.OPERATIONAL, userTokens_iid, usertokens, true); - - CheckedFuture commitFuture = tx.submit(); - Futures.addCallback(commitFuture, new FutureCallback() { - - @Override - public void onSuccess(Void result) { - LOG.trace("Token {} was written to datastore.", claims.getToken()); - LOG.trace("Tokenlist for userId {} was written to datastore.", - tokenlist.getUserId()); - } - - @Override - public void onFailure(Throwable t) { - LOG.error("Inserting token {} to datastore failed.", claims.getToken()); - LOG.trace("Inserting for userId {} tokenlist to datastore failed.", - tokenlist.getUserId()); - } - - }); - } - - private Claims readClaims(String token) { - final InstanceIdentifier claims_iid = AuthNStoreUtil.createInstIdentifierForTokencache(token); - Claims claims = null; - ReadTransaction rt = broker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> claimsFuture = rt.read( - LogicalDatastoreType.OPERATIONAL, claims_iid); - try { - Optional maybeClaims = claimsFuture.checkedGet(); - if (maybeClaims.isPresent()) { - claims = maybeClaims.get(); - } - } catch (ReadFailedException e) { - LOG.error( - "Something wrong happened in DataStore. Getting Claim for token {} failed.", - token, e); - } - return claims; - } - - private TokenList readTokenListFromDS(String userId) { - InstanceIdentifier tokenList_iid = InstanceIdentifier.builder( - TokenCacheTimes.class).child(TokenList.class, new TokenListKey(userId)).build(); - TokenList tokenList = null; - ReadTransaction rt = broker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> userTokenListFuture = rt.read( - LogicalDatastoreType.OPERATIONAL, tokenList_iid); - try { - Optional maybeTokenList = userTokenListFuture.checkedGet(); - if (maybeTokenList.isPresent()) { - tokenList = maybeTokenList.get(); - } - } catch (ReadFailedException e) { - LOG.error( - "Something wrong happened in DataStore. Getting TokenList for userId {} failed.", - userId, e); - } - return tokenList; - } - - private UserTokens readUserTokensFromDS(String token, String userId) { - final InstanceIdentifier userTokens_iid = AuthNStoreUtil.createInstIdentifierUserTokens( - userId, token); - UserTokens userTokens = null; - - ReadTransaction rt = broker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> userTokensFuture = rt.read( - LogicalDatastoreType.OPERATIONAL, userTokens_iid); - - try { - Optional maybeUserTokens = userTokensFuture.checkedGet(); - if (maybeUserTokens.isPresent()) { - userTokens = maybeUserTokens.get(); - } - } catch (ReadFailedException e) { - LOG.error( - "Something wrong happened in DataStore. Getting UserTokens for token {} failed.", - token, e); - } - - return userTokens; - } - - private boolean deleteClaims(String token) { - final InstanceIdentifier claims_iid = AuthNStoreUtil.createInstIdentifierForTokencache(token); - boolean result = false; - WriteTransaction tx = broker.newWriteOnlyTransaction(); - tx.delete(LogicalDatastoreType.OPERATIONAL, claims_iid); - CheckedFuture commitFuture = tx.submit(); - - try { - commitFuture.checkedGet(); - result = true; - } catch (TransactionCommitFailedException e) { - LOG.error("Something wrong happened in DataStore. Claim " - + "deletion for token {} from DataStore failed.", token, e); - } - return result; - } - - private void deleteUserTokenFromDS(String token, String userId) { - final InstanceIdentifier userTokens_iid = AuthNStoreUtil.createInstIdentifierUserTokens( - userId, token); - - WriteTransaction tx = broker.newWriteOnlyTransaction(); - tx.delete(LogicalDatastoreType.OPERATIONAL, userTokens_iid); - CheckedFuture commitFuture = tx.submit(); - try { - commitFuture.checkedGet(); - } catch (TransactionCommitFailedException e) { - LOG.error("Something wrong happened in DataStore. UserToken " - + "deletion for token {} from DataStore failed.", token, e); - } - } - - private Runnable deleteOldTokens(final Claims claims) { - return new Runnable() { - - @Override - public void run() { - TokenList tokenList = null; - if (claims != null) { - tokenList = readTokenListFromDS(claims.getUserId()); - } - if (tokenList != null) { - for (UserTokens currUserToken : tokenList.getUserTokens()) { - long diff = System.currentTimeMillis() - - currUserToken.getTimestamp().longValue(); - if (diff > currUserToken.getExpiration() - && currUserToken.getExpiration() != 0) { - if (deleteClaims(currUserToken.getTokenid())) { - deleteUserTokenFromDS(currUserToken.getTokenid(), - claims.getUserId()); - LOG.trace("Expired tokens for UserId {} deleted.", - claims.getUserId()); - } - } - } - } - } - }; - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypter.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypter.java deleted file mode 100644 index ca0a74be..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypter.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store; - -import java.security.spec.KeySpec; -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; -import javax.xml.bind.DatatypeConverter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author - Sharon Aicler (saichler@cisco.com) - **/ -public class DataEncrypter { - - final protected SecretKey k; - private static final Logger LOG = LoggerFactory.getLogger(DataEncrypter.class); - private static final byte[] iv = { 0, 5, 0, 0, 7, 81, 0, 3, 0, 0, 0, 0, 0, 43, 0, 1 }; - private static final IvParameterSpec ivspec = new IvParameterSpec(iv); - public static final String ENCRYPTED_TAG = "Encrypted:"; - - public DataEncrypter(final String ckey) { - SecretKey tmp = null; - if (ckey != null && !ckey.isEmpty()) { - - try { - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - KeySpec spec = new PBEKeySpec(ckey.toCharArray(), iv, 32768, 128); - tmp = keyFactory.generateSecret(spec); - } catch (Exception e) { - LOG.error("Couldn't initialize key factory", e); - } - if (tmp != null) { - k = new SecretKeySpec(tmp.getEncoded(), "AES"); - } else { - throw new RuntimeException("Couldn't initalize encryption key"); - } - } else { - k = null; - LOG.warn("Void crypto key passed! AuthN Store Encryption disabled"); - } - - } - - protected String encrypt(String token) { - - if (k == null) { - return token; - } - - String cryptostring = null; - try { - Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); - c.init(Cipher.ENCRYPT_MODE, k, ivspec); - byte[] cryptobytes = c.doFinal(token.getBytes()); - cryptostring = DatatypeConverter.printBase64Binary(cryptobytes); - return ENCRYPTED_TAG + cryptostring; - } catch (Exception e) { - LOG.error("Couldn't encrypt token", e); - return null; - } - } - - protected String decrypt(String eToken) { - if (k == null) { - return eToken; - } - - if (eToken == null || eToken.length() == 0) { - return null; - } - - if (!eToken.startsWith(ENCRYPTED_TAG)) { - return eToken; - } - - try { - Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); - c.init(Cipher.DECRYPT_MODE, k, ivspec); - - byte[] cryptobytes = DatatypeConverter.parseBase64Binary(eToken.substring(ENCRYPTED_TAG.length())); - byte[] clearbytes = c.doFinal(cryptobytes); - return DatatypeConverter.printBase64Binary(clearbytes); - - } catch (Exception e) { - LOG.error("Couldn't decrypt token", e); - return null; - } - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMMDSALStore.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMMDSALStore.java deleted file mode 100644 index 88fba0ba..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMMDSALStore.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.authn.mdsal.store; - -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.CheckedFuture; -import java.util.List; -import java.util.concurrent.ExecutionException; -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.aaa.api.SHA256Calculator; -import org.opendaylight.controller.md.sal.binding.api.DataBroker; -import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; -import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; -import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.Authentication; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.DomainBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.DomainKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.GrantBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.GrantKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.RoleBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.RoleKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.UserBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.UserKey; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Sharon Aicler - saichler@cisco.com - * - */ -public class IDMMDSALStore { - - private static final Logger LOG = LoggerFactory.getLogger(IDMMDSALStore.class); - private final DataBroker dataBroker; - - public IDMMDSALStore(DataBroker dataBroker) { - this.dataBroker = dataBroker; - } - - public static final String getString(String aValue, String bValue) { - if (aValue != null) - return aValue; - return bValue; - } - - public static final Boolean getBoolean(Boolean aValue, Boolean bValue) { - if (aValue != null) - return aValue; - return bValue; - } - - public static boolean waitForSubmit(CheckedFuture submit) { - // This can happen only when testing - if (submit == null) - return false; - while (!submit.isDone() && !submit.isCancelled()) { - try { - Thread.sleep(1000); - } catch (Exception err) { - LOG.error("Interrupted", err); - } - } - return submit.isCancelled(); - } - - // Domain methods - public Domain writeDomain(Domain domain) { - Preconditions.checkNotNull(domain); - Preconditions.checkNotNull(domain.getName()); - Preconditions.checkNotNull(domain.isEnabled()); - DomainBuilder b = new DomainBuilder(); - b.setDescription(domain.getDescription()); - b.setDomainid(domain.getName()); - b.setEnabled(domain.isEnabled()); - b.setName(domain.getName()); - b.setKey(new DomainKey(b.getName())); - domain = b.build(); - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Domain.class, new DomainKey(domain.getDomainid())); - WriteTransaction wrt = dataBroker.newWriteOnlyTransaction(); - wrt.put(LogicalDatastoreType.CONFIGURATION, ID, domain, true); - CheckedFuture submit = wrt.submit(); - if (!waitForSubmit(submit)) { - return domain; - } else { - return null; - } - } - - public Domain readDomain(String domainid) { - Preconditions.checkNotNull(domainid); - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Domain.class, new DomainKey(domainid)); - ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> read = rot.read( - LogicalDatastoreType.CONFIGURATION, ID); - if (read == null) { - LOG.error("Failed to read domain from data store"); - return null; - } - Optional optional = null; - try { - optional = read.get(); - } catch (InterruptedException | ExecutionException e1) { - LOG.error("Failed to read domain from data store", e1); - return null; - } - - if (optional == null) - return null; - - if (!optional.isPresent()) - return null; - - return optional.get(); - } - - public Domain deleteDomain(String domainid) { - Preconditions.checkNotNull(domainid); - Domain domain = readDomain(domainid); - if (domain == null) { - LOG.error("Failed to delete domain from data store, unknown domain"); - return null; - } - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Domain.class, new DomainKey(domainid)); - WriteTransaction wrt = dataBroker.newWriteOnlyTransaction(); - wrt.delete(LogicalDatastoreType.CONFIGURATION, ID); - wrt.submit(); - return domain; - } - - public Domain updateDomain(Domain domain) throws IDMStoreException { - Preconditions.checkNotNull(domain); - Preconditions.checkNotNull(domain.getDomainid()); - Domain existing = readDomain(domain.getDomainid()); - DomainBuilder b = new DomainBuilder(); - b.setDescription(getString(domain.getDescription(), existing.getDescription())); - b.setName(existing.getName()); - b.setEnabled(getBoolean(domain.isEnabled(), existing.isEnabled())); - return writeDomain(b.build()); - } - - public List getAllDomains() { - InstanceIdentifier id = InstanceIdentifier.create(Authentication.class); - ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> read = rot.read( - LogicalDatastoreType.CONFIGURATION, id); - if (read == null) - return null; - - try { - if (read.get() == null) - return null; - if (read.get().isPresent()) { - Authentication auth = read.get().get(); - return auth.getDomain(); - } - } catch (Exception err) { - LOG.error("Failed to read domains", err); - } - return null; - } - - public List getAllRoles() { - InstanceIdentifier id = InstanceIdentifier.create(Authentication.class); - ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> read = rot.read( - LogicalDatastoreType.CONFIGURATION, id); - if (read == null) - return null; - - try { - if (read.get() == null) - return null; - if (read.get().isPresent()) { - Authentication auth = read.get().get(); - return auth.getRole(); - } - } catch (Exception err) { - LOG.error("Failed to read domains", err); - } - return null; - } - - public List getAllUsers() { - InstanceIdentifier id = InstanceIdentifier.create(Authentication.class); - ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> read = rot.read( - LogicalDatastoreType.CONFIGURATION, id); - if (read == null) - return null; - - try { - if (read.get() == null) - return null; - if (read.get().isPresent()) { - Authentication auth = read.get().get(); - return auth.getUser(); - } - } catch (Exception err) { - LOG.error("Failed to read domains", err); - } - return null; - } - - public List getAllGrants() { - InstanceIdentifier id = InstanceIdentifier.create(Authentication.class); - ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> read = rot.read( - LogicalDatastoreType.CONFIGURATION, id); - if (read == null) - return null; - - try { - if (read.get() == null) - return null; - if (read.get().isPresent()) { - Authentication auth = read.get().get(); - return auth.getGrant(); - } - } catch (Exception err) { - LOG.error("Failed to read domains", err); - } - return null; - } - - // Role methods - public Role writeRole(Role role) { - Preconditions.checkNotNull(role); - Preconditions.checkNotNull(role.getName()); - Preconditions.checkNotNull(role.getDomainid()); - Preconditions.checkNotNull(readDomain(role.getDomainid())); - RoleBuilder b = new RoleBuilder(); - b.setDescription(role.getDescription()); - b.setRoleid(IDMStoreUtil.createRoleid(role.getName(), role.getDomainid())); - b.setKey(new RoleKey(b.getRoleid())); - b.setName(role.getName()); - b.setDomainid(role.getDomainid()); - role = b.build(); - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Role.class, new RoleKey(role.getRoleid())); - WriteTransaction wrt = dataBroker.newWriteOnlyTransaction(); - wrt.put(LogicalDatastoreType.CONFIGURATION, ID, role, true); - CheckedFuture submit = wrt.submit(); - if (!waitForSubmit(submit)) { - return role; - } else { - return null; - } - } - - public Role readRole(String roleid) { - Preconditions.checkNotNull(roleid); - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Role.class, new RoleKey(roleid)); - ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> read = rot.read( - LogicalDatastoreType.CONFIGURATION, ID); - if (read == null) { - LOG.error("Failed to read role from data store"); - return null; - } - Optional optional = null; - try { - optional = read.get(); - } catch (InterruptedException | ExecutionException e1) { - LOG.error("Failed to read role from data store", e1); - return null; - } - - if (optional == null) - return null; - - if (!optional.isPresent()) - return null; - - return optional.get(); - } - - public Role deleteRole(String roleid) { - Preconditions.checkNotNull(roleid); - Role role = readRole(roleid); - if (role == null) { - LOG.error("Failed to delete role from data store, unknown role"); - return null; - } - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Role.class, new RoleKey(roleid)); - WriteTransaction wrt = dataBroker.newWriteOnlyTransaction(); - wrt.delete(LogicalDatastoreType.CONFIGURATION, ID); - wrt.submit(); - return role; - } - - public Role updateRole(Role role) { - Preconditions.checkNotNull(role); - Preconditions.checkNotNull(role.getRoleid()); - Role existing = readRole(role.getRoleid()); - RoleBuilder b = new RoleBuilder(); - b.setDescription(getString(role.getDescription(), existing.getDescription())); - b.setName(existing.getName()); - b.setDomainid(existing.getDomainid()); - return writeRole(b.build()); - } - - // User methods - public User writeUser(User user) throws IDMStoreException { - Preconditions.checkNotNull(user); - Preconditions.checkNotNull(user.getName()); - Preconditions.checkNotNull(user.getDomainid()); - Preconditions.checkNotNull(readDomain(user.getDomainid())); - UserBuilder b = new UserBuilder(); - if (user.getSalt() == null) { - b.setSalt(SHA256Calculator.generateSALT()); - } else { - b.setSalt(user.getSalt()); - } - b.setUserid(IDMStoreUtil.createUserid(user.getName(), user.getDomainid())); - b.setDescription(user.getDescription()); - b.setDomainid(user.getDomainid()); - b.setEmail(user.getEmail()); - b.setEnabled(user.isEnabled()); - b.setKey(new UserKey(b.getUserid())); - b.setName(user.getName()); - b.setPassword(SHA256Calculator.getSHA256(user.getPassword(), b.getSalt())); - user = b.build(); - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - User.class, new UserKey(user.getUserid())); - WriteTransaction wrt = dataBroker.newWriteOnlyTransaction(); - wrt.put(LogicalDatastoreType.CONFIGURATION, ID, user, true); - CheckedFuture submit = wrt.submit(); - if (!waitForSubmit(submit)) { - return user; - } else { - return null; - } - } - - public User readUser(String userid) { - Preconditions.checkNotNull(userid); - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - User.class, new UserKey(userid)); - ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> read = rot.read( - LogicalDatastoreType.CONFIGURATION, ID); - if (read == null) { - LOG.error("Failed to read user from data store"); - return null; - } - Optional optional = null; - try { - optional = read.get(); - } catch (InterruptedException | ExecutionException e1) { - LOG.error("Failed to read domain from data store", e1); - return null; - } - - if (optional == null) - return null; - - if (!optional.isPresent()) - return null; - - return optional.get(); - } - - public User deleteUser(String userid) { - Preconditions.checkNotNull(userid); - User user = readUser(userid); - if (user == null) { - LOG.error("Failed to delete user from data store, unknown user"); - return null; - } - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - User.class, new UserKey(userid)); - WriteTransaction wrt = dataBroker.newWriteOnlyTransaction(); - wrt.delete(LogicalDatastoreType.CONFIGURATION, ID); - wrt.submit(); - return user; - } - - public User updateUser(User user) throws IDMStoreException { - Preconditions.checkNotNull(user); - Preconditions.checkNotNull(user.getUserid()); - User existing = readUser(user.getUserid()); - UserBuilder b = new UserBuilder(); - b.setName(existing.getName()); - b.setDomainid(existing.getDomainid()); - b.setDescription(getString(user.getDescription(), existing.getDescription())); - b.setEmail(getString(user.getEmail(), existing.getEmail())); - b.setEnabled(getBoolean(user.isEnabled(), existing.isEnabled())); - b.setPassword(getString(user.getPassword(), existing.getPassword())); - b.setSalt(getString(user.getSalt(), existing.getSalt())); - return writeUser(b.build()); - } - - // Grant methods - public Grant writeGrant(Grant grant) throws IDMStoreException { - Preconditions.checkNotNull(grant); - Preconditions.checkNotNull(grant.getDomainid()); - Preconditions.checkNotNull(grant.getUserid()); - Preconditions.checkNotNull(grant.getRoleid()); - Preconditions.checkNotNull(readDomain(grant.getDomainid())); - Preconditions.checkNotNull(readUser(grant.getUserid())); - Preconditions.checkNotNull(readRole(grant.getRoleid())); - GrantBuilder b = new GrantBuilder(); - b.setDomainid(grant.getDomainid()); - b.setRoleid(grant.getRoleid()); - b.setUserid(grant.getUserid()); - b.setGrantid(IDMStoreUtil.createGrantid(grant.getUserid(), grant.getDomainid(), - grant.getRoleid())); - b.setKey(new GrantKey(b.getGrantid())); - grant = b.build(); - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Grant.class, new GrantKey(grant.getGrantid())); - WriteTransaction wrt = dataBroker.newWriteOnlyTransaction(); - wrt.put(LogicalDatastoreType.CONFIGURATION, ID, grant, true); - CheckedFuture submit = wrt.submit(); - if (!waitForSubmit(submit)) { - return grant; - } else { - return null; - } - } - - public Grant readGrant(String grantid) { - Preconditions.checkNotNull(grantid); - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Grant.class, new GrantKey(grantid)); - ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> read = rot.read( - LogicalDatastoreType.CONFIGURATION, ID); - if (read == null) { - LOG.error("Failed to read grant from data store"); - return null; - } - Optional optional = null; - try { - optional = read.get(); - } catch (InterruptedException | ExecutionException e1) { - LOG.error("Failed to read domain from data store", e1); - return null; - } - - if (optional == null) - return null; - - if (!optional.isPresent()) - return null; - - return optional.get(); - } - - public Grant deleteGrant(String grantid) { - Preconditions.checkNotNull(grantid); - Grant grant = readGrant(grantid); - if (grant == null) { - LOG.error("Failed to delete grant from data store, unknown grant"); - return null; - } - InstanceIdentifier ID = InstanceIdentifier.create(Authentication.class).child( - Grant.class, new GrantKey(grantid)); - WriteTransaction wrt = dataBroker.newWriteOnlyTransaction(); - wrt.delete(LogicalDatastoreType.CONFIGURATION, ID); - wrt.submit(); - return grant; - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMObject2MDSAL.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMObject2MDSAL.java deleted file mode 100644 index 0b58ced7..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMObject2MDSAL.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.authn.mdsal.store; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.DomainBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.GrantBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.RoleBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.UserBuilder; -import org.opendaylight.yangtools.yang.binding.DataObject; -/** - * - * @author saichler@gmail.com - * - * This class is a codec to convert between MDSAL objects and IDM model objects. It is doing so via reflection when it assumes that the MDSAL - * Object and the IDM model object has the same method names. - */ -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Sharon Aicler - saichler@cisco.com - * - */ -public abstract class IDMObject2MDSAL { - private static final Logger LOG = LoggerFactory.getLogger(IDMObject2MDSAL.class); - // this is a Map mapping between the class type of the IDM Model object to a - // structure containing the corresponding setters and getter methods - // in MDSAL object - private static Map, ConvertionMethods> typesMethods = new HashMap, ConvertionMethods>(); - - // This method generically via reflection receive a MDSAL object and the - // corresponding IDM model object class type and - // creates an IDM model element from the MDSAL element - private static Object fromMDSALObject(Object mdsalObject, Class type) throws Exception { - if (mdsalObject == null) - return null; - Object result = type.newInstance(); - ConvertionMethods cm = typesMethods.get(type); - if (cm == null) { - cm = new ConvertionMethods(); - typesMethods.put(type, cm); - Method methods[] = type.getMethods(); - for (Method m : methods) { - if (m.getName().startsWith("set")) { - cm.setMethods.add(m); - Method gm = null; - if (m.getParameterTypes()[0].equals(Boolean.class) - || m.getParameterTypes()[0].equals(boolean.class)) - gm = ((DataObject) mdsalObject).getImplementedInterface().getMethod( - "is" + m.getName().substring(3), (Class[]) null); - else { - try { - gm = ((DataObject) mdsalObject).getImplementedInterface().getMethod( - "get" + m.getName().substring(3), (Class[]) null); - } catch (Exception err) { - LOG.error("Error associating get call", err); - } - } - cm.getMethods.put(m.getName(), gm); - } - } - } - for (Method m : cm.setMethods) { - try { - m.invoke( - result, - new Object[] { cm.getMethods.get(m.getName()).invoke(mdsalObject, - (Object[]) null) }); - } catch (Exception err) { - LOG.error("Error invoking reflection method", err); - } - } - return result; - } - - // This method generically use reflection to receive an IDM model object and - // the corresponsing MDSAL object and creates - // a MDSAL object out of the IDM model object - private static Object toMDSALObject(Object object, Class mdSalBuilderType) throws Exception { - if (object == null) - return null; - Object result = mdSalBuilderType.newInstance(); - ConvertionMethods cm = typesMethods.get(mdSalBuilderType); - if (cm == null) { - cm = new ConvertionMethods(); - typesMethods.put(mdSalBuilderType, cm); - Method methods[] = mdSalBuilderType.getMethods(); - for (Method m : methods) { - if (m.getName().startsWith("set")) { - try { - Method gm = null; - if (m.getParameterTypes()[0].equals(Boolean.class) - || m.getParameterTypes()[0].equals(boolean.class)) - gm = object.getClass().getMethod("is" + m.getName().substring(3), - (Class[]) null); - else - gm = object.getClass().getMethod("get" + m.getName().substring(3), - (Class[]) null); - cm.getMethods.put(m.getName(), gm); - cm.setMethods.add(m); - } catch (NoSuchMethodException err) { - } - } - } - cm.builderMethod = mdSalBuilderType.getMethod("build", (Class[]) null); - } - for (Method m : cm.setMethods) { - m.invoke(result, - new Object[] { cm.getMethods.get(m.getName()).invoke(object, (Object[]) null) }); - } - - return cm.builderMethod.invoke(result, (Object[]) null); - } - - // A struccture class to hold the getters & setters of each type to speed - // things up - private static class ConvertionMethods { - private List setMethods = new ArrayList(); - private Map getMethods = new HashMap(); - private Method builderMethod = null; - } - - // Convert Domain - public static org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain toMDSALDomain( - Domain domain) { - try { - return (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain) toMDSALObject( - domain, DomainBuilder.class); - } catch (Exception err) { - LOG.error("Error converting domain to MDSAL object", err); - return null; - } - } - - public static Domain toIDMDomain( - org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain domain) { - try { - return (Domain) fromMDSALObject(domain, Domain.class); - } catch (Exception err) { - LOG.error("Error converting domain from MDSAL to IDM object", err); - return null; - } - } - - // Convert Role - public static org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role toMDSALRole( - Role role) { - try { - return (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role) toMDSALObject( - role, RoleBuilder.class); - } catch (Exception err) { - LOG.error("Error converting role to MDSAL object", err); - return null; - } - } - - public static Role toIDMRole( - org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role role) { - try { - return (Role) fromMDSALObject(role, Role.class); - } catch (Exception err) { - LOG.error("Error converting role fom MDSAL to IDM object", err); - return null; - } - } - - // Convert User - public static org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User toMDSALUser( - User user) { - try { - return (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User) toMDSALObject( - user, UserBuilder.class); - } catch (Exception err) { - LOG.error("Error converting user to MDSAL object", err); - return null; - } - } - - public static User toIDMUser( - org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User user) { - try { - return (User) fromMDSALObject(user, User.class); - } catch (Exception err) { - LOG.error("Error converting user from MDSAL to IDM object", err); - return null; - } - } - - // Convert Grant - public static org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant toMDSALGrant( - Grant grant) { - try { - return (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant) toMDSALObject( - grant, GrantBuilder.class); - } catch (Exception err) { - LOG.error("Error converting grant to MDSAL object", err); - return null; - } - } - - public static Grant toIDMGrant( - org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant grant) { - try { - return (Grant) fromMDSALObject(grant, Grant.class); - } catch (Exception err) { - LOG.error("Error converting grant from MDSAL to IDM object", err); - return null; - } - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMStore.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMStore.java deleted file mode 100644 index 69bc1d52..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMStore.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.authn.mdsal.store; - -import java.util.List; -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Domains; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Grants; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.Roles; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.aaa.api.model.Users; - -/** - * @author Sharon Aicler - saichler@cisco.com - * - */ -public class IDMStore implements IIDMStore { - private final IDMMDSALStore mdsalStore; - - public IDMStore(IDMMDSALStore mdsalStore) { - this.mdsalStore = mdsalStore; - } - - @Override - public Domain writeDomain(Domain domain) throws IDMStoreException { - return IDMObject2MDSAL.toIDMDomain(mdsalStore.writeDomain(IDMObject2MDSAL.toMDSALDomain(domain))); - } - - @Override - public Domain readDomain(String domainid) throws IDMStoreException { - return IDMObject2MDSAL.toIDMDomain(mdsalStore.readDomain(domainid)); - } - - @Override - public Domain deleteDomain(String domainid) throws IDMStoreException { - return IDMObject2MDSAL.toIDMDomain(mdsalStore.deleteDomain(domainid)); - } - - @Override - public Domain updateDomain(Domain domain) throws IDMStoreException { - return IDMObject2MDSAL.toIDMDomain(mdsalStore.updateDomain(IDMObject2MDSAL.toMDSALDomain(domain))); - } - - @Override - public Domains getDomains() throws IDMStoreException { - Domains domains = new Domains(); - List mdSalDomains = mdsalStore.getAllDomains(); - for (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain d : mdSalDomains) { - domains.getDomains().add(IDMObject2MDSAL.toIDMDomain(d)); - } - return domains; - } - - @Override - public Role writeRole(Role role) throws IDMStoreException { - return IDMObject2MDSAL.toIDMRole(mdsalStore.writeRole(IDMObject2MDSAL.toMDSALRole(role))); - } - - @Override - public Role readRole(String roleid) throws IDMStoreException { - return IDMObject2MDSAL.toIDMRole(mdsalStore.readRole(roleid)); - } - - @Override - public Role deleteRole(String roleid) throws IDMStoreException { - return IDMObject2MDSAL.toIDMRole(mdsalStore.deleteRole(roleid)); - } - - @Override - public Role updateRole(Role role) throws IDMStoreException { - return IDMObject2MDSAL.toIDMRole(mdsalStore.writeRole(IDMObject2MDSAL.toMDSALRole(role))); - } - - @Override - public User writeUser(User user) throws IDMStoreException { - return IDMObject2MDSAL.toIDMUser(mdsalStore.writeUser(IDMObject2MDSAL.toMDSALUser(user))); - } - - @Override - public User readUser(String userid) throws IDMStoreException { - return IDMObject2MDSAL.toIDMUser(mdsalStore.readUser(userid)); - } - - @Override - public User deleteUser(String userid) throws IDMStoreException { - return IDMObject2MDSAL.toIDMUser(mdsalStore.deleteUser(userid)); - } - - @Override - public User updateUser(User user) throws IDMStoreException { - return IDMObject2MDSAL.toIDMUser(mdsalStore.writeUser(IDMObject2MDSAL.toMDSALUser(user))); - } - - @Override - public Grant writeGrant(Grant grant) throws IDMStoreException { - return IDMObject2MDSAL.toIDMGrant(mdsalStore.writeGrant(IDMObject2MDSAL.toMDSALGrant(grant))); - } - - @Override - public Grant readGrant(String grantid) throws IDMStoreException { - return IDMObject2MDSAL.toIDMGrant(mdsalStore.readGrant(grantid)); - } - - @Override - public Grant deleteGrant(String grantid) throws IDMStoreException { - return IDMObject2MDSAL.toIDMGrant(mdsalStore.readGrant(grantid)); - } - - @Override - public Roles getRoles() throws IDMStoreException { - Roles roles = new Roles(); - List mdSalRoles = mdsalStore.getAllRoles(); - for (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role r : mdSalRoles) { - roles.getRoles().add(IDMObject2MDSAL.toIDMRole(r)); - } - return roles; - } - - @Override - public Users getUsers() throws IDMStoreException { - Users users = new Users(); - List mdSalUsers = mdsalStore.getAllUsers(); - for (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User u : mdSalUsers) { - users.getUsers().add(IDMObject2MDSAL.toIDMUser(u)); - } - return users; - } - - @Override - public Users getUsers(String username, String domain) throws IDMStoreException { - Users users = new Users(); - List mdSalUsers = mdsalStore.getAllUsers(); - for (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User u : mdSalUsers) { - if (u.getDomainid().equals(domain) && u.getName().equals(username)) { - users.getUsers().add(IDMObject2MDSAL.toIDMUser(u)); - } - } - return users; - } - - @Override - public Grants getGrants(String domainid, String userid) throws IDMStoreException { - Grants grants = new Grants(); - List mdSalGrants = mdsalStore.getAllGrants(); - String currentGrantUserId, currentGrantDomainId; - for (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant g : mdSalGrants) { - currentGrantUserId = g.getUserid(); - currentGrantDomainId = g.getDomainid(); - if (currentGrantUserId.equals(userid) && currentGrantDomainId.equals(domainid)) { - grants.getGrants().add(IDMObject2MDSAL.toIDMGrant(g)); - } - } - return grants; - } - - @Override - public Grants getGrants(String userid) throws IDMStoreException { - Grants grants = new Grants(); - List mdSalGrants = mdsalStore.getAllGrants(); - for (org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant g : mdSalGrants) { - if (g.getUserid().equals(userid)) { - grants.getGrants().add(IDMObject2MDSAL.toIDMGrant(g)); - } - } - return grants; - } - - @Override - public Grant readGrant(String domainid, String userid, String roleid) throws IDMStoreException { - return readGrant(IDMStoreUtil.createGrantid(userid, domainid, roleid)); - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtil.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtil.java deleted file mode 100644 index 6ef58109..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtil.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store.util; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import org.opendaylight.aaa.AuthenticationBuilder; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.TokenCacheTimes; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.Tokencache; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.TokenList; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.TokenListBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.TokenListKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.token_list.UserTokens; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.token_list.UserTokensBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.token_list.UserTokensKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.tokencache.Claims; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.tokencache.ClaimsBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.tokencache.ClaimsKey; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; - -public class AuthNStoreUtil { - - public static InstanceIdentifier createInstIdentifierForTokencache(String token) { - if (token == null || token.length() == 0) - return null; - - InstanceIdentifier claims_iid = InstanceIdentifier.builder(Tokencache.class) - .child(Claims.class, - new ClaimsKey(token)) - .build(); - return claims_iid; - } - - public static InstanceIdentifier createInstIdentifierUserTokens(String userId, - String token) { - if (userId == null || userId.length() == 0 || token == null || token.length() == 0) - return null; - - InstanceIdentifier userTokens_iid = InstanceIdentifier.builder( - TokenCacheTimes.class) - .child(TokenList.class, - new TokenListKey( - userId)) - .child(UserTokens.class, - new UserTokensKey( - token)) - .build(); - return userTokens_iid; - } - - public static Claims createClaimsRecord(String token, Authentication auth) { - if (auth == null || token == null || token.length() == 0) - return null; - - ClaimsKey claimsKey = new ClaimsKey(token); - ClaimsBuilder claimsBuilder = new ClaimsBuilder(); - claimsBuilder.setClientId(auth.clientId()); - claimsBuilder.setDomain(auth.domain()); - claimsBuilder.setKey(claimsKey); - List roles = new ArrayList(); - roles.addAll(auth.roles()); - claimsBuilder.setRoles(roles); - claimsBuilder.setToken(token); - claimsBuilder.setUser(auth.user()); - claimsBuilder.setUserId(auth.userId()); - return claimsBuilder.build(); - } - - public static UserTokens createUserTokens(String token, Long expiration) { - if (expiration == null || token == null || token.length() == 0) - return null; - - UserTokensBuilder userTokensBuilder = new UserTokensBuilder(); - userTokensBuilder.setTokenid(token); - BigInteger timestamp = BigInteger.valueOf(System.currentTimeMillis()); - userTokensBuilder.setTimestamp(timestamp); - userTokensBuilder.setExpiration(expiration); - userTokensBuilder.setKey(new UserTokensKey(token)); - return userTokensBuilder.build(); - } - - public static TokenList createTokenList(UserTokens tokens, String userId) { - if (tokens == null || userId == null || userId.length() == 0) - return null; - - TokenListBuilder tokenListBuilder = new TokenListBuilder(); - tokenListBuilder.setUserId(userId); - tokenListBuilder.setKey(new TokenListKey(userId)); - List userTokens = new ArrayList(); - userTokens.add(tokens); - tokenListBuilder.setUserTokens(userTokens); - return tokenListBuilder.build(); - } - - public static Authentication convertClaimToAuthentication(final Claims claims, Long expiration) { - if (claims == null) - return null; - - Claim claim = new Claim() { - @Override - public String clientId() { - return claims.getClientId(); - } - - @Override - public String userId() { - return claims.getUserId(); - } - - @Override - public String user() { - return claims.getUser(); - } - - @Override - public String domain() { - return claims.getDomain(); - } - - @Override - public Set roles() { - return new HashSet<>(claims.getRoles()); - } - }; - AuthenticationBuilder authBuilder = new AuthenticationBuilder(claim); - authBuilder.setExpiration(expiration); - return authBuilder.build(); - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/mdsal/store/rev141031/AuthNStoreModule.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/mdsal/store/rev141031/AuthNStoreModule.java deleted file mode 100644 index 0631170e..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/mdsal/store/rev141031/AuthNStoreModule.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - * - */ - -package org.opendaylight.yang.gen.v1.config.aaa.authn.mdsal.store.rev141031; - -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.api.TokenStore; -import org.opendaylight.aaa.authn.mdsal.store.AuthNStore; -import org.opendaylight.aaa.authn.mdsal.store.IDMMDSALStore; -import org.opendaylight.aaa.authn.mdsal.store.IDMStore; -import org.opendaylight.controller.md.sal.binding.api.DataBroker; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; - -public class AuthNStoreModule - extends - org.opendaylight.yang.gen.v1.config.aaa.authn.mdsal.store.rev141031.AbstractAuthNStoreModule { - private BundleContext bundleContext; - - public AuthNStoreModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, - org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { - super(identifier, dependencyResolver); - } - - public AuthNStoreModule( - org.opendaylight.controller.config.api.ModuleIdentifier identifier, - org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, - org.opendaylight.yang.gen.v1.config.aaa.authn.mdsal.store.rev141031.AuthNStoreModule oldModule, - java.lang.AutoCloseable oldInstance) { - super(identifier, dependencyResolver, oldModule, oldInstance); - } - - @Override - public void customValidation() { - // add custom validation form module attributes here. - } - - @Override - public java.lang.AutoCloseable createInstance() { - - DataBroker dataBrokerService = getDataBrokerDependency(); - final AuthNStore authNStore = new AuthNStore(dataBrokerService, getPassword()); - final IDMMDSALStore mdsalStore = new IDMMDSALStore(dataBrokerService); - final IDMStore idmStore = new IDMStore(mdsalStore); - - authNStore.setTimeToLive(getTimeToLive()); - - // Register the MD-SAL Token store with OSGI - final ServiceRegistration serviceRegistration = bundleContext.registerService( - TokenStore.class.getName(), authNStore, null); - final ServiceRegistration idmServiceRegistration = bundleContext.registerService( - IIDMStore.class.getName(), idmStore, null); - final class AutoCloseableStore implements AutoCloseable { - - @Override - public void close() throws Exception { - serviceRegistration.unregister(); - idmServiceRegistration.unregister(); - authNStore.close(); - } - } - - return new AutoCloseableStore(); - - // return authNStore; - - // throw new java.lang.UnsupportedOperationException(); - } - - /** - * @param bundleContext - */ - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - /** - * @return the bundleContext - */ - public BundleContext getBundleContext() { - return bundleContext; - } - -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/mdsal/store/rev141031/AuthNStoreModuleFactory.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/mdsal/store/rev141031/AuthNStoreModuleFactory.java deleted file mode 100644 index b1e278fa..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/mdsal/store/rev141031/AuthNStoreModuleFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - * - */ - -/* - * Generated file - * - * Generated from: yang module name: aaa-authn-mdsal-store-cfg yang module local name: aaa-authn-mdsal-store - * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator - * Generated at: Thu Mar 19 18:06:18 CET 2015 - * - * Do not modify this file unless it is present under src/main directory - */ -package org.opendaylight.yang.gen.v1.config.aaa.authn.mdsal.store.rev141031; - -import org.opendaylight.controller.config.api.DependencyResolver; -import org.osgi.framework.BundleContext; - -public class AuthNStoreModuleFactory - extends - org.opendaylight.yang.gen.v1.config.aaa.authn.mdsal.store.rev141031.AbstractAuthNStoreModuleFactory { - - @Override - public AuthNStoreModule instantiateModule(String instanceName, - DependencyResolver dependencyResolver, BundleContext bundleContext) { - AuthNStoreModule module = super.instantiateModule(instanceName, dependencyResolver, - bundleContext); - module.setBundleContext(bundleContext); - return module; - } - - @Override - public AuthNStoreModule instantiateModule(String instanceName, - DependencyResolver dependencyResolver, AuthNStoreModule oldModule, - AutoCloseable oldInstance, BundleContext bundleContext) { - AuthNStoreModule module = super.instantiateModule(instanceName, dependencyResolver, - oldModule, oldInstance, bundleContext); - module.setBundleContext(bundleContext); - return module; - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/yang/aaa-authn-mdsal-store-cfg.yang b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/yang/aaa-authn-mdsal-store-cfg.yang deleted file mode 100644 index eac344b8..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/yang/aaa-authn-mdsal-store-cfg.yang +++ /dev/null @@ -1,77 +0,0 @@ -module aaa-authn-mdsal-store-cfg { - - yang-version 1; - namespace "config:aaa:authn:mdsal:store"; - prefix "aaa-authn-store-cfg"; - - import config { prefix config; revision-date 2013-04-05; } - import rpc-context { prefix rpcx; revision-date 2013-06-17; } - import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; } - import opendaylight-md-sal-dom {prefix dom;} - - - description - "This module contains the base YANG definitions for - AuthN MD-SAL backed data cache implementation."; - - revision "2014-10-31" { - description - "Initial revision."; - } - - identity token-store-service{ - base config:service-type; - config:java-class "org.opendaylight.aaa.api.TokenStore"; - } - - - // This is the definition of the service implementation as a module identity. - identity aaa-authn-mdsal-store { - base config:module-type; - // Specifies the prefix for generated java classes. - config:java-name-prefix AuthNStore; - config:provided-service token-store-service; - } - - // Augments the 'configuration' choice node under modules/module. - - augment "/config:modules/config:module/config:configuration" { - case aaa-authn-mdsal-store { - when "/config:modules/config:module/config:type = 'aaa-authn-mdsal-store'"; - - //Defines reference to the Bundle context and MD-SAL data broker - container dom-broker { - uses config:service-ref { - refine type { - mandatory true; - config:required-identity dom:dom-broker-osgi-registry; - } - } - } - container data-broker { - uses config:service-ref { - refine type { - mandatory true; - config:required-identity mdsal:binding-async-data-broker; - - } - } - } - - leaf timeToLive { - description "Time to live for tokens. When set to 0 = never expire"; - type uint64; - default 360000; - } - leaf timeToWait { - description "Time to wait for future from data store. 10 by default = never expire"; - type uint16; - default 10; - } - leaf password { - description "Encryption password for the Store"; - type string; - } - } - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataBrokerReadMocker.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataBrokerReadMocker.java deleted file mode 100644 index f821cf16..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataBrokerReadMocker.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class DataBrokerReadMocker implements InvocationHandler { - private Map> stubs = new HashMap>(); - private Class mokingClass = null; - - @Override - public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable { - List stList = stubs.get(arg1); - if (stList != null) { - for (StubContainer sc : stList) { - if (sc.fitGeneric(arg2)) { - return sc.returnObject; - } - } - } - return null; - } - - public DataBrokerReadMocker(Class cls) { - this.mokingClass = cls; - } - - public static Object addMock(Class cls) { - return Proxy.newProxyInstance(cls.getClassLoader(), new Class[] { cls }, - new DataBrokerReadMocker(cls)); - } - - public static DataBrokerReadMocker getMocker(Object o) { - return (DataBrokerReadMocker) Proxy.getInvocationHandler(o); - } - - public static Method findMethod(Class cls, String name, Object args[]) { - Method methods[] = cls.getMethods(); - for (Method m : methods) { - if (m.getName().equals(name)) { - if ((m.getParameterTypes() == null || m.getParameterTypes().length == 0) - && args == null) { - return m; - } - boolean match = true; - for (int i = 0; i < m.getParameterTypes().length; i++) { - if (!m.getParameterTypes()[i].isAssignableFrom(args[i].getClass())) { - match = false; - } - } - if (match) - return m; - } - } - return null; - } - - public void addWhen(String methodName, Object[] args, Object returnThis) - throws NoSuchMethodException, SecurityException { - Method m = findMethod(this.mokingClass, methodName, args); - if (m == null) - throw new IllegalArgumentException("Unable to find method"); - StubContainer sc = new StubContainer(args, returnThis); - List lst = stubs.get(m); - if (lst == null) { - lst = new ArrayList<>(); - } - lst.add(sc); - stubs.put(m, lst); - } - - private class StubContainer { - private Class[] parameters = null; - private Class[] generics = null; - private Object args[] = null; - private Object returnObject; - - public StubContainer(Object[] _args, Object ret) { - this.args = _args; - this.returnObject = ret; - } - - public boolean fitGeneric(Object _args[]) { - if (args == null && _args != null) - return false; - if (args != null && _args == null) - return false; - if (args == null && _args == null) - return true; - if (args.length != _args.length) - return false; - for (int i = 0; i < args.length; i++) { - if (!args[i].equals(_args[i])) { - return false; - } - } - return true; - } - } -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypterTest.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypterTest.java deleted file mode 100644 index eec69bc0..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypterTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store; - -import static org.junit.Assert.assertEquals; - -import javax.xml.bind.DatatypeConverter; -import org.junit.Test; - -public class DataEncrypterTest { - - @Test - public void testEncrypt() { - DataEncrypter dataEncry = new DataEncrypter("foo_key_test"); - String token = "foo_token_test"; - String eToken = dataEncry.encrypt(token); - // check for decryption result - String returnToken = dataEncry.decrypt(eToken); - String tokenBase64 = DatatypeConverter.printBase64Binary(token.getBytes()); - assertEquals(tokenBase64, returnToken); - } - - @Test - public void testDecrypt() { - DataEncrypter dataEncry = new DataEncrypter("foo_key_test"); - String eToken = "foo_etoken_test"; - assertEquals(dataEncry.decrypt(""), null); - // check for encryption Tag - assertEquals(eToken, dataEncry.decrypt(eToken)); - } - -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTest.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTest.java deleted file mode 100644 index f376dd5f..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTest.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store; - -import org.junit.Assert; -import org.junit.Test; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.aaa.api.SHA256Calculator; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User; - -public class IDMStoreTest { - - @Test - public void testWriteDomain() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoFordomain(); - Domain domain = testedObject.writeDomain(util.domain); - Assert.assertNotNull(domain); - Assert.assertEquals(domain.getDomainid(), util.domain.getName()); - } - - @Test - public void testReadDomain() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoFordomain(); - Domain domain = testedObject.readDomain(util.domain.getDomainid()); - Assert.assertNotNull(domain); - Assert.assertEquals(domain, util.domain); - } - - @Test - public void testDeleteDomain() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoFordomain(); - Domain domain = testedObject.deleteDomain(util.domain.getDomainid()); - Assert.assertEquals(domain, util.domain); - } - - @Test - public void testUpdateDomain() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoFordomain(); - Domain domain = testedObject.updateDomain(util.domain); - Assert.assertEquals(domain, util.domain); - } - - @Test - public void testWriteRole() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForrole(); - util.addMokitoFordomain(); - Role role = testedObject.writeRole(util.role); - Assert.assertNotNull(role); - Assert.assertEquals(role.getRoleid(), - IDMStoreUtil.createRoleid(role.getName(), role.getDomainid())); - } - - @Test - public void testReadRole() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForrole(); - Role role = testedObject.readRole(util.role.getRoleid()); - Assert.assertNotNull(role); - Assert.assertEquals(role, util.role); - } - - @Test - public void testDeleteRole() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForrole(); - Role role = testedObject.deleteRole(util.role.getRoleid()); - Assert.assertNotNull(role); - Assert.assertEquals(role, util.role); - } - - @Test - public void testUpdateRole() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForrole(); - Role role = testedObject.updateRole(util.role); - Assert.assertNotNull(role); - Assert.assertEquals(role, util.role); - } - - @Test - public void testWriteUser() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForuser(); - User user = testedObject.writeUser(util.user); - Assert.assertNotNull(user); - Assert.assertEquals(user.getUserid(), - IDMStoreUtil.createUserid(user.getName(), util.user.getDomainid())); - } - - @Test - public void testReadUser() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForuser(); - User user = testedObject.readUser(util.user.getUserid()); - Assert.assertNotNull(user); - Assert.assertEquals(user, util.user); - } - - @Test - public void testDeleteUser() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForuser(); - User user = testedObject.deleteUser(util.user.getUserid()); - Assert.assertNotNull(user); - Assert.assertEquals(user, util.user); - } - - @Test - public void testUpdateUser() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForuser(); - User user = testedObject.updateUser(util.user); - Assert.assertNotNull(user); - Assert.assertEquals(user.getPassword(), - SHA256Calculator.getSHA256(util.user.getPassword(), util.user.getSalt())); - } - - @Test - public void testWriteGrant() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoFordomain(); - util.addMokitoForrole(); - util.addMokitoForuser(); - util.addMokitoForgrant(); - Grant grant = testedObject.writeGrant(util.grant); - Assert.assertNotNull(grant); - } - - @Test - public void testReadGrant() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForgrant(); - Grant grant = testedObject.readGrant(util.grant.getGrantid()); - Assert.assertNotNull(grant); - Assert.assertEquals(grant, util.grant); - } - - @Test - public void testDeleteGrant() throws Exception { - IDMStoreTestUtil util = new IDMStoreTestUtil(); - IDMMDSALStore testedObject = new IDMMDSALStore(util.dataBroker); - util.addMokitoForgrant(); - Grant grant = testedObject.deleteGrant(util.grant.getGrantid()); - Assert.assertNotNull(grant); - Assert.assertEquals(grant, util.grant); - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTestUtil.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTestUtil.java deleted file mode 100644 index 39eeadb4..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTestUtil.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import com.google.common.base.Optional; -import com.google.common.util.concurrent.CheckedFuture; -import java.util.concurrent.ExecutionException; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.controller.md.sal.binding.api.DataBroker; -import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; -import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.Authentication; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.DomainBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.DomainKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.GrantBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.GrantKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.RoleBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.RoleKey; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.UserBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.UserKey; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; - -public class IDMStoreTestUtil { - /* DataBroker mocked with Mokito */ - protected static DataBroker dataBroker = mock(DataBroker.class); - protected static WriteTransaction wrt = mock(WriteTransaction.class); - protected static ReadOnlyTransaction rot = null; - - static { - rot = (ReadOnlyTransaction) DataBrokerReadMocker.addMock(ReadOnlyTransaction.class); - when(dataBroker.newReadOnlyTransaction()).thenReturn(rot); - when(dataBroker.newWriteOnlyTransaction()).thenReturn(wrt); - } - - /* Domain Data Object Instance */ - public Domain domain = createdomain(); - - /* Domain create Method */ - public Domain createdomain() { - /* Start of Domain builder */ - DomainBuilder domainbuilder = new DomainBuilder(); - domainbuilder.setName("SETNAME"); - domainbuilder.setDomainid("SETNAME"); - domainbuilder.setKey(new DomainKey("SETNAME")); - domainbuilder.setDescription("SETDESCRIPTION"); - domainbuilder.setEnabled(true); - /* End of Domain builder */ - return domainbuilder.build(); - } - - /* Role Data Object Instance */ - public Role role = createrole(); - - /* Role create Method */ - public Role createrole() { - /* Start of Role builder */ - RoleBuilder rolebuilder = new RoleBuilder(); - rolebuilder.setRoleid("SETNAME@SETNAME"); - rolebuilder.setName("SETNAME"); - rolebuilder.setKey(new RoleKey(rolebuilder.getRoleid())); - rolebuilder.setDomainid(createdomain().getDomainid()); - rolebuilder.setDescription("SETDESCRIPTION"); - /* End of Role builder */ - return rolebuilder.build(); - } - - /* User Data Object Instance */ - public User user = createuser(); - - /* User create Method */ - public User createuser() { - /* Start of User builder */ - UserBuilder userbuilder = new UserBuilder(); - userbuilder.setUserid("SETNAME@SETNAME"); - userbuilder.setName("SETNAME"); - userbuilder.setKey(new UserKey(userbuilder.getUserid())); - userbuilder.setDomainid(createdomain().getDomainid()); - userbuilder.setEmail("SETEMAIL"); - userbuilder.setPassword("SETPASSWORD"); - userbuilder.setSalt("SETSALT"); - userbuilder.setEnabled(true); - userbuilder.setDescription("SETDESCRIPTION"); - /* End of User builder */ - return userbuilder.build(); - } - - /* Grant Data Object Instance */ - public Grant grant = creategrant(); - - /* Grant create Method */ - public Grant creategrant() { - /* Start of Grant builder */ - GrantBuilder grantbuilder = new GrantBuilder(); - grantbuilder.setDomainid(createdomain().getDomainid()); - grantbuilder.setRoleid(createrole().getRoleid()); - grantbuilder.setUserid(createuser().getUserid()); - grantbuilder.setGrantid(IDMStoreUtil.createGrantid(grantbuilder.getUserid(), - grantbuilder.getDomainid(), grantbuilder.getRoleid())); - grantbuilder.setKey(new GrantKey(grantbuilder.getGrantid())); - /* End of Grant builder */ - return grantbuilder.build(); - } - - /* InstanceIdentifier for Grant instance grant */ - public InstanceIdentifier grantID = InstanceIdentifier.create(Authentication.class) - .child(Grant.class, - creategrant().getKey()); - - /* Mokito DataBroker method for grant Data Object */ - public void addMokitoForgrant() throws NoSuchMethodException, SecurityException, InterruptedException, ExecutionException { - CheckedFuture, ReadFailedException> read = mock(CheckedFuture.class); - DataBrokerReadMocker.getMocker(rot).addWhen("read", - new Object[] { LogicalDatastoreType.CONFIGURATION, grantID }, read); - Optional optional = mock(Optional.class); - when(read.get()).thenReturn(optional); - when(optional.get()).thenReturn(grant); - when(optional.isPresent()).thenReturn(true); - } - - /* InstanceIdentifier for Domain instance domain */ - public InstanceIdentifier domainID = InstanceIdentifier.create(Authentication.class) - .child(Domain.class, - new DomainKey( - new String( - "SETNAME"))); - - /* Mokito DataBroker method for domain Data Object */ - public void addMokitoFordomain() throws NoSuchMethodException, SecurityException, InterruptedException, ExecutionException { - CheckedFuture, ReadFailedException> read = mock(CheckedFuture.class); - DataBrokerReadMocker.getMocker(rot).addWhen("read", - new Object[] { LogicalDatastoreType.CONFIGURATION, domainID }, read); - Optional optional = mock(Optional.class); - when(read.get()).thenReturn(optional); - when(optional.get()).thenReturn(domain); - when(optional.isPresent()).thenReturn(true); - } - - /* InstanceIdentifier for Role instance role */ - public InstanceIdentifier roleID = InstanceIdentifier.create(Authentication.class).child( - Role.class, createrole().getKey()); - - /* Mokito DataBroker method for role Data Object */ - public void addMokitoForrole() throws NoSuchMethodException, SecurityException, InterruptedException, ExecutionException { - CheckedFuture, ReadFailedException> read = mock(CheckedFuture.class); - DataBrokerReadMocker.getMocker(rot).addWhen("read", - new Object[] { LogicalDatastoreType.CONFIGURATION, roleID }, read); - Optional optional = mock(Optional.class); - when(read.get()).thenReturn(optional); - when(optional.get()).thenReturn(role); - when(optional.isPresent()).thenReturn(true); - } - - /* InstanceIdentifier for User instance user */ - public InstanceIdentifier userID = InstanceIdentifier.create(Authentication.class).child( - User.class, createuser().getKey()); - - /* Mokito DataBroker method for user Data Object */ - public void addMokitoForuser() throws NoSuchMethodException, SecurityException, InterruptedException, ExecutionException { - CheckedFuture, ReadFailedException> read = mock(CheckedFuture.class); - DataBrokerReadMocker.getMocker(rot).addWhen("read", - new Object[] { LogicalDatastoreType.CONFIGURATION, userID }, read); - Optional optional = mock(Optional.class); - when(read.get()).thenReturn(optional); - when(optional.get()).thenReturn(user); - when(optional.isPresent()).thenReturn(true); - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/MDSALConvertTest.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/MDSALConvertTest.java deleted file mode 100644 index 9b7c9712..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/MDSALConvertTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store; - -import org.junit.Assert; -import org.junit.Test; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.User; - -public class MDSALConvertTest { - @Test - public void testConvertDomain() { - Domain d = new Domain(); - d.setDescription("hello"); - d.setDomainid("hello"); - d.setEnabled(true); - d.setName("Hello"); - org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain mdsalDomain = IDMObject2MDSAL.toMDSALDomain(d); - Assert.assertNotNull(mdsalDomain); - Domain d2 = IDMObject2MDSAL.toIDMDomain(mdsalDomain); - Assert.assertNotNull(d2); - Assert.assertEquals(d, d2); - } - - @Test - public void testConvertRole() { - Role r = new Role(); - r.setDescription("hello"); - r.setRoleid("Hello@hello"); - r.setName("Hello"); - r.setDomainid("hello"); - org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role mdsalRole = IDMObject2MDSAL.toMDSALRole(r); - Assert.assertNotNull(mdsalRole); - Role r2 = IDMObject2MDSAL.toIDMRole(mdsalRole); - Assert.assertNotNull(r2); - Assert.assertEquals(r, r2); - } - - @Test - public void testConvertUser() { - User u = new User(); - u.setDescription("hello"); - u.setDomainid("hello"); - u.setUserid("hello@hello"); - u.setName("Hello"); - u.setEmail("email"); - u.setEnabled(true); - u.setPassword("pass"); - u.setSalt("salt"); - org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User mdsalUser = IDMObject2MDSAL.toMDSALUser(u); - Assert.assertNotNull(mdsalUser); - User u2 = IDMObject2MDSAL.toIDMUser(mdsalUser); - Assert.assertNotNull(u2); - Assert.assertEquals(u, u2); - } - - @Test - public void testConvertGrant() { - Grant g = new Grant(); - g.setDomainid("hello"); - g.setUserid("hello@hello"); - g.setRoleid("hello@hello"); - g.setGrantid("hello@hello@Hello"); - org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant mdsalGrant = IDMObject2MDSAL.toMDSALGrant(g); - Assert.assertNotNull(mdsalGrant); - Grant g2 = IDMObject2MDSAL.toIDMGrant(mdsalGrant); - Assert.assertNotNull(g2); - Assert.assertEquals(g, g2); - } -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtilTest.java b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtilTest.java deleted file mode 100644 index 10c18790..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtilTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authn.mdsal.store.util; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.token_cache_times.token_list.UserTokens; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.tokencache.Claims; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.tokencache.ClaimsBuilder; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.tokencache.ClaimsKey; -import org.powermock.modules.junit4.PowerMockRunner; - -@RunWith(PowerMockRunner.class) -public class AuthNStoreUtilTest { - - private String token = "foo_token_test"; - private String userId = "123"; - private Long expire = new Long(365); - @Mock - private Authentication auth; - @Mock - private UserTokens tokens; - @Mock - private Claims claims; - - @Test - public void testCreateInstIdentifierForTokencache() { - assertTrue(AuthNStoreUtil.createInstIdentifierForTokencache("") == null); - assertNotNull(AuthNStoreUtil.createInstIdentifierForTokencache(token)); - } - - @Test - public void testCreateInstIdentifierUserTokens() { - assertTrue(AuthNStoreUtil.createInstIdentifierUserTokens("", "") == null); - assertNotNull(AuthNStoreUtil.createInstIdentifierUserTokens(userId, token)); - } - - @Test - public void testCreateClaimsRecord() { - assertTrue(AuthNStoreUtil.createClaimsRecord("", null) == null); - assertNotNull(AuthNStoreUtil.createClaimsRecord(token, auth)); - } - - @Test - public void testCreateUserTokens() { - assertTrue(AuthNStoreUtil.createUserTokens("", null) == null); - assertNotNull(AuthNStoreUtil.createUserTokens(token, expire)); - } - - @Test - public void testCreateTokenList() { - assertTrue(AuthNStoreUtil.createTokenList(null, "") == null); - assertNotNull(AuthNStoreUtil.createTokenList(tokens, userId)); - } - - @Test - public void testConvertClaimToAuthentication() { - ClaimsKey claimsKey = new ClaimsKey(token); - ClaimsBuilder claimsBuilder = new ClaimsBuilder(); - claimsBuilder.setClientId("123"); - claimsBuilder.setDomain("foo_domain"); - claimsBuilder.setKey(claimsKey); - List roles = new ArrayList(); - roles.add("foo_role"); - claimsBuilder.setRoles(roles); - claimsBuilder.setToken(token); - claimsBuilder.setUser("foo_usr"); - claimsBuilder.setUserId(userId); - Claims fooClaims = claimsBuilder.build(); - - assertTrue(AuthNStoreUtil.convertClaimToAuthentication(null, expire) == null); - assertNotNull(AuthNStoreUtil.convertClaimToAuthentication(fooClaims, expire)); - } - -} diff --git a/odl-aaa-moon/aaa-authn-mdsal-store/pom.xml b/odl-aaa-moon/aaa-authn-mdsal-store/pom.xml deleted file mode 100644 index 38d29147..00000000 --- a/odl-aaa-moon/aaa-authn-mdsal-store/pom.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - 4.0.0 - - aaa-authn-mdsal-store - ${project.artifactId} - pom - - - aaa-authn-mdsal-api - aaa-authn-mdsal-config - aaa-authn-mdsal-store-impl - - diff --git a/odl-aaa-moon/aaa-authn-sssd/pom.xml b/odl-aaa-moon/aaa-authn-sssd/pom.xml deleted file mode 100644 index b70c2466..00000000 --- a/odl-aaa-moon/aaa-authn-sssd/pom.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn-sssd - bundle - - - - org.opendaylight.aaa - aaa-authn - - - org.opendaylight.aaa - aaa-authn-api - - - org.glassfish - javax.json - - - org.opendaylight.aaa - aaa-authn-idpmapping - - - org.slf4j - slf4j-api - - - com.sun.jersey - jersey-server - provided - - - javax.servlet - javax.servlet-api - provided - - - org.osgi - org.osgi.core - provided - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - - com.sun.jersey.jersey-test-framework - jersey-test-framework-grizzly2 - test - - - junit - junit - test - - - org.slf4j - slf4j-simple - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.opendaylight.aaa.sssd.Activator - - ${project.basedir}/META-INF - - - - - diff --git a/odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/Activator.java b/odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/Activator.java deleted file mode 100644 index b6d5259f..00000000 --- a/odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/Activator.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sssd; - -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.opendaylight.aaa.api.ClaimAuth; -import org.osgi.framework.BundleContext; - -public class Activator extends DependencyActivatorBase { - - @Override - public void init(BundleContext context, DependencyManager manager) throws Exception { - manager.add(createComponent().setInterface(new String[] { ClaimAuth.class.getName() }, null) - .setImplementation(SssdClaimAuth.class)); - } - - @Override - public void destroy(BundleContext context, DependencyManager manager) throws Exception { - } - -} diff --git a/odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/SssdClaimAuth.java b/odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/SssdClaimAuth.java deleted file mode 100644 index 0ae23b48..00000000 --- a/odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/SssdClaimAuth.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sssd; - -import java.io.StringWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.json.Json; -import javax.json.JsonValue; -import javax.json.stream.JsonGenerator; -import javax.json.stream.JsonGeneratorFactory; -import org.apache.felix.dm.Component; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.api.ClaimAuth; -import org.opendaylight.aaa.idpmapping.RuleProcessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An SSSD {@link ClaimAuth} implementation. - * - * @author John Dennis <jdennis@redhat.com> - */ -public class SssdClaimAuth implements ClaimAuth { - private static final Logger LOG = LoggerFactory.getLogger(SssdClaimAuth.class); - - private static final String DEFAULT_MAPPING_RULES_PATHNAME = "etc/idp_mapping_rules.json"; - private JsonGeneratorFactory generatorFactory = null; - private RuleProcessor ruleProcessor = null; - - // Called by DM when all required dependencies are satisfied. - void init(Component c) { - LOG.info("Initializing SSSD Plugin"); - Map properties = new HashMap(1); - properties.put(JsonGenerator.PRETTY_PRINTING, true); - generatorFactory = Json.createGeneratorFactory(properties); - - String mappingRulesFile = DEFAULT_MAPPING_RULES_PATHNAME; - if (mappingRulesFile == null || mappingRulesFile.isEmpty()) { - LOG.warn("mapping rules file is not configured, " + "SssdClaimAuth will be disabled"); - return; - } - - Path mappingRulesPath = Paths.get(mappingRulesFile); - - if (!Files.exists(mappingRulesPath)) { - LOG.warn(String.format("mapping rules file (%s) " - + "does not exist, SssdClaimAuth will be disabled", mappingRulesFile)); - return; - } - - try { - ruleProcessor = new RuleProcessor(mappingRulesPath, null); - } catch (Exception e) { - LOG.error(String.format("mapping rules file (%s) " - + "could not be loaded, SssdClaimAuth will be disabled. " + "error = %s", - mappingRulesFile, e)); - } - } - - /** - * Transform a Map of assertions into a {@link Claim} via a set of mapping - * rules. - * - * A set of mapping rules have been previously loaded. the incoming - * assertion is converted to a JSON document and presented to the - * {@link RuleProcessor}. If the RuleProcessor can successfully transform - * the assertion given the site specific set of rules it will return a Map - * of values which will then be used to build a {@link Claim}. The rule - * should return one or more of the following which will be used to populate - * the Claim. - * - *
- *
ClientId
- *
A string. - * - * @see org.opendaylight.aaa.api.Claim#clientId()
- * - *
UserId
A string. - * @see org.opendaylight.aaa.api.Claim#userId()
- * - *
User
A string. - * @see org.opendaylight.aaa.api.Claim#user()
- * - *
Domain
A string. - * @see org.opendaylight.aaa.api.Claim#domain()
- * - *
Roles
An array of strings. - * @see org.opendaylight.aaa.api.Claim#roles()
- * - *
- * - * @param assertion - * A Map of name/value assertions provided by an external IdP - * @return A {@link Claim} if successful, null otherwise. - */ - - @Override - public Claim transform(Map assertion) { - String assertionJson; - Map mapped; - assertionJson = claimToJson(assertion); - - if (ruleProcessor == null) { - LOG.debug("ruleProcessor not configured"); - return null; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("assertionJson=\n{}", assertionJson); - } - - mapped = ruleProcessor.process(assertionJson); - if (mapped == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("RuleProcessor returned null"); - } - return null; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("RuleProcessor returned: {}", mapped); - } - - ClaimBuilder cb = new ClaimBuilder(); - if (mapped.containsKey("ClientId")) { - cb.setClientId((String) mapped.get("ClientId")); - } - if (mapped.containsKey("UserId")) { - cb.setUserId((String) mapped.get("UserId")); - } - if (mapped.containsKey("User")) { - cb.setUser((String) mapped.get("User")); - } - if (mapped.containsKey("Domain")) { - cb.setDomain((String) mapped.get("Domain")); - } - if (mapped.containsKey("Roles")) { - @SuppressWarnings("unchecked") - List roles = (List) mapped.get("roles"); - for (String role : roles) { - cb.addRole(role); - } - } - Claim claim = cb.build(); - - if (LOG.isDebugEnabled()) { - LOG.debug("returns claim = {}", claim.toString()); - } - - return claim; - } - - /** - * Convert a Claim Map into a JSON object. - * - * Given a Map of name/value pairs convert it into a JSON object and return - * it as a string. This is not a general purpose routine used to convert any - * Map into JSON because a claim has the restriction that each value must be - * a scalar and those scalars are restricted to the following types: - * - *
    - *
  • String
  • - *
  • Integer
  • - *
  • Long
  • - *
  • Double
  • - *
  • Boolean
  • - *
  • null
  • - *
- * - * See also {@link ClaimAuth}. - * - * @param claim - * The Map containing assertion claims to be converted into a - * JSON assertion document. - * @return A string formatted as a JSON object. - */ - - public String claimToJson(Map claim) { - StringWriter stringWriter = new StringWriter(); - JsonGenerator generator = generatorFactory.createGenerator(stringWriter); - - generator.writeStartObject(); - for (Map.Entry entry : claim.entrySet()) { - String name = entry.getKey(); - Object value = entry.getValue(); - - if (value instanceof String) { - generator.write(name, (String) value); - } else if (value instanceof Integer) { - generator.write(name, ((Integer) value).intValue()); - } else if (value instanceof Long) { - generator.write(name, ((Long) value).longValue()); - } else if (value instanceof Double) { - generator.write(name, ((Double) value).doubleValue()); - } else if (value instanceof Boolean) { - generator.write(name, ((Boolean) value).booleanValue()); - } else if (value == null) { - generator.write(name, JsonValue.NULL); - } else { - LOG.warn(String.format("ignoring claim unsupported value type " - + "entry %s has type %s", name, value.getClass().getSimpleName())); - } - } - generator.writeEnd(); - generator.close(); - return stringWriter.toString(); - } -} diff --git a/odl-aaa-moon/aaa-authn-store/pom.xml b/odl-aaa-moon/aaa-authn-store/pom.xml deleted file mode 100644 index 744c4df1..00000000 --- a/odl-aaa-moon/aaa-authn-store/pom.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn-store - 0.3.1-Beryllium-SR1 - bundle - - - - net.sf.ehcache - ehcache - - - org.opendaylight.aaa - aaa-authn-api - - - org.slf4j - slf4j-api - - - org.osgi - org.osgi.core - provided - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - - junit - junit - test - - - org.mockito - mockito-all - test - - - org.slf4j - slf4j-simple - test - - - org.opendaylight.aaa - aaa-authn - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.opendaylight.aaa.store.Activator - - ${project.basedir}/META-INF - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - package - - attach-artifact - - - - - ${project.build.directory}/classes/tokens.cfg - cfg - config - - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/Activator.java b/odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/Activator.java deleted file mode 100644 index f3299723..00000000 --- a/odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/Activator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.store; - -import java.util.Dictionary; -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.opendaylight.aaa.api.TokenStore; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.service.cm.ManagedService; - -/** - * An activator for the default datastore implementation of {@link TokenStore}. - * - * @author liemmn - */ -public class Activator extends DependencyActivatorBase { - - private static final String TOKEN_PID = "org.opendaylight.aaa.tokens"; - - @Override - public void init(BundleContext context, DependencyManager manager) throws Exception { - DefaultTokenStore ts = new DefaultTokenStore(); - manager.add(createComponent().setInterface(new String[] { TokenStore.class.getName() }, - null).setImplementation(ts)); - context.registerService(ManagedService.class.getName(), ts, - addPid(DefaultTokenStore.defaults)); - } - - @Override - public void destroy(BundleContext context, DependencyManager manager) throws Exception { - } - - private Dictionary addPid(Dictionary dict) { - dict.put(Constants.SERVICE_PID, TOKEN_PID); - return dict; - } -} diff --git a/odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/DefaultTokenStore.java b/odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/DefaultTokenStore.java deleted file mode 100644 index df65be32..00000000 --- a/odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/DefaultTokenStore.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.store; - -import java.io.File; -import java.lang.management.ManagementFactory; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.concurrent.locks.ReentrantLock; -import javax.management.MBeanServer; -import net.sf.ehcache.Cache; -import net.sf.ehcache.CacheManager; -import net.sf.ehcache.Element; -import net.sf.ehcache.config.CacheConfiguration; -import net.sf.ehcache.management.ManagementService; -import org.apache.felix.dm.Component; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.TokenStore; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A default token store for STS. - * - * @author liemmn - * - */ -public class DefaultTokenStore implements TokenStore, ManagedService { - private static final Logger LOG = LoggerFactory.getLogger(DefaultTokenStore.class); - private static final String TOKEN_STORE_CONFIG_ERR = "Token store configuration error"; - - private static final String TOKEN_CACHE_MANAGER = "org.opendaylight.aaa"; - private static final String TOKEN_CACHE = "tokens"; - private static final String EHCACHE_XML = "etc/ehcache.xml"; - - static final String MAX_CACHED_MEMORY = "maxCachedTokensInMemory"; - static final String MAX_CACHED_DISK = "maxCachedTokensOnDisk"; - static final String SECS_TO_LIVE = "secondsToLive"; - static final String SECS_TO_IDLE = "secondsToIdle"; - - // Defaults (needed only for non-Karaf deployments) - static final Dictionary defaults = new Hashtable<>(); - static { - defaults.put(MAX_CACHED_MEMORY, Long.toString(10000)); - defaults.put(MAX_CACHED_DISK, Long.toString(1000000)); - defaults.put(SECS_TO_IDLE, Long.toString(3600)); - defaults.put(SECS_TO_LIVE, Long.toString(3600)); - } - - // Token cache lock - private static final ReentrantLock cacheLock = new ReentrantLock(); - - // Token cache - private Cache tokens; - - // This should be a singleton - DefaultTokenStore() { - } - - // Called by DM when all required dependencies are satisfied. - void init(Component c) { - File ehcache = new File(EHCACHE_XML); - CacheManager cm; - if (ehcache.exists()) { - cm = CacheManager.create(ehcache.getAbsolutePath()); - tokens = cm.getCache(TOKEN_CACHE); - LOG.info("Initialized token store with custom cache config"); - } else { - cm = CacheManager.getInstance(); - tokens = new Cache( - new CacheConfiguration(TOKEN_CACHE, - Integer.parseInt(defaults.get(MAX_CACHED_MEMORY))).maxEntriesLocalDisk( - Integer.parseInt(defaults.get(MAX_CACHED_DISK))) - .timeToLiveSeconds( - Long.parseLong(defaults.get(SECS_TO_LIVE))) - .timeToIdleSeconds( - Long.parseLong(defaults.get(SECS_TO_IDLE)))); - cm.addCache(tokens); - LOG.info("Initialized token store with default cache config"); - } - cm.setName(TOKEN_CACHE_MANAGER); - - // JMX for cache management - MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); - ManagementService.registerMBeans(cm, mBeanServer, false, false, false, true); - } - - // Called on shutdown - void destroy() { - LOG.info("Shutting down token store..."); - CacheManager.getInstance().shutdown(); - } - - @Override - public Authentication get(String token) { - Element elem = tokens.get(token); - return (Authentication) ((elem != null) ? elem.getObjectValue() : null); - } - - @Override - public void put(String token, Authentication auth) { - tokens.put(new Element(token, auth)); - } - - @Override - public boolean delete(String token) { - return tokens.remove(token); - } - - @Override - public long tokenExpiration() { - return tokens.getCacheConfiguration().getTimeToLiveSeconds(); - } - - @Override - public void updated(@SuppressWarnings("rawtypes") Dictionary props) - throws ConfigurationException { - LOG.info("Updating token store configuration..."); - if (props == null) { - // Someone deleted the configuration, use defaults - props = defaults; - } - reconfig(props); - } - - // Refresh cache configuration... - private void reconfig(@SuppressWarnings("rawtypes") Dictionary props) - throws ConfigurationException { - cacheLock.lock(); - try { - long secsToIdle = Long.parseLong(props.get(SECS_TO_IDLE).toString()); - long secsToLive = Long.parseLong(props.get(SECS_TO_LIVE).toString()); - int maxMem = Integer.parseInt(props.get(MAX_CACHED_MEMORY).toString()); - int maxDisk = Integer.parseInt(props.get(MAX_CACHED_DISK).toString()); - CacheConfiguration config = tokens.getCacheConfiguration(); - config.setTimeToIdleSeconds(secsToIdle); - config.setTimeToLiveSeconds(secsToLive); - config.maxEntriesLocalHeap(maxMem); - config.maxEntriesLocalDisk(maxDisk); - } catch (Throwable t) { - throw new ConfigurationException(null, TOKEN_STORE_CONFIG_ERR, t); - } finally { - cacheLock.unlock(); - } - } -} diff --git a/odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.properties b/odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.properties deleted file mode 100644 index b88d5c10..00000000 --- a/odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.properties +++ /dev/null @@ -1,14 +0,0 @@ -org.opendaylight.aaa.tokens.name = Opendaylight AAA Token Configuration -org.opendaylight.aaa.tokens.description = Configuration for AAA tokens -org.opendaylight.aaa.tokens.maxCachedTokensInMemory.name = Memory Configuration -org.opendaylight.aaa.tokens.maxCachedTokensInMemory.description = Maximum number of \ -tokens in memory -org.opendaylight.aaa.tokens.maxCachedTokensOnDisk.name = Disk Configuration -org.opendaylight.aaa.tokens.maxCachedTokensOnDisk.description = Maximum number of \ -tokens in memory -org.opendaylight.aaa.tokens.secondsToLive.name = Token Expiration -org.opendaylight.aaa.tokens.secondsToLive.description = Maximum number of \ -seconds a token can exist regardless of use. Zero (0) means never expires. -org.opendaylight.aaa.tokens.secondsToIdle.name = Unused Token Expiration -org.opendaylight.aaa.tokens.secondsToIdle.description = Maximum number of \ -seconds a token can exist without being accessed. Zero (0) means never expires. \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.xml b/odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.xml deleted file mode 100644 index d04874f4..00000000 --- a/odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-store/src/main/resources/tokens.cfg b/odl-aaa-moon/aaa-authn-store/src/main/resources/tokens.cfg deleted file mode 100644 index d3dda90e..00000000 --- a/odl-aaa-moon/aaa-authn-store/src/main/resources/tokens.cfg +++ /dev/null @@ -1,4 +0,0 @@ -maxCachedTokensInMemory=10000 -maxCachedTokensOnDisk=1000000 -secondsToLive=3600 -secondsToIdle=3600 \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn-store/src/test/java/org/opendaylight/aaa/store/DefaultTokenStoreTest.java b/odl-aaa-moon/aaa-authn-store/src/test/java/org/opendaylight/aaa/store/DefaultTokenStoreTest.java deleted file mode 100644 index e5c837bf..00000000 --- a/odl-aaa-moon/aaa-authn-store/src/test/java/org/opendaylight/aaa/store/DefaultTokenStoreTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.store; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.mock; -import static org.opendaylight.aaa.store.DefaultTokenStore.MAX_CACHED_DISK; -import static org.opendaylight.aaa.store.DefaultTokenStore.MAX_CACHED_MEMORY; -import static org.opendaylight.aaa.store.DefaultTokenStore.SECS_TO_IDLE; -import static org.opendaylight.aaa.store.DefaultTokenStore.SECS_TO_LIVE; - -import java.util.Dictionary; -import java.util.Hashtable; -import org.apache.felix.dm.Component; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.opendaylight.aaa.AuthenticationBuilder; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.api.Authentication; -import org.osgi.service.cm.ConfigurationException; - -public class DefaultTokenStoreTest { - private static final String FOO_TOKEN = "foo_token"; - private final DefaultTokenStore dts = new DefaultTokenStore(); - private static final Dictionary config = new Hashtable<>(); - static { - config.put(MAX_CACHED_MEMORY, Long.toString(3)); - config.put(MAX_CACHED_DISK, Long.toString(3)); - config.put(SECS_TO_IDLE, Long.toString(1)); - config.put(SECS_TO_LIVE, Long.toString(1)); - } - - @Before - public void setup() throws ConfigurationException { - dts.init(mock(Component.class)); - dts.updated(config); - } - - @After - public void teardown() { - dts.destroy(); - } - - @Test - public void testCache() throws InterruptedException { - Authentication auth = new AuthenticationBuilder(new ClaimBuilder().setUser("foo") - .setUserId("1234") - .addRole("admin").build()).build(); - dts.put(FOO_TOKEN, auth); - assertEquals(auth, dts.get(FOO_TOKEN)); - dts.delete(FOO_TOKEN); - assertNull(dts.get(FOO_TOKEN)); - dts.put(FOO_TOKEN, auth); - Thread.sleep(1200); - assertNull(dts.get(FOO_TOKEN)); - } - -} diff --git a/odl-aaa-moon/aaa-authn-sts/pom.xml b/odl-aaa-moon/aaa-authn-sts/pom.xml deleted file mode 100644 index 25ac0fe6..00000000 --- a/odl-aaa-moon/aaa-authn-sts/pom.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn-sts - bundle - - - - org.opendaylight.aaa - aaa-authn - - - org.opendaylight.aaa - aaa-authn-api - - - org.slf4j - slf4j-api - - - com.sun.jersey - jersey-server - provided - - - javax.servlet - javax.servlet-api - provided - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - provided - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.common - provided - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.resourceserver - provided - - - org.osgi - org.osgi.core - provided - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - - com.sun.jersey.jersey-test-framework - jersey-test-framework-grizzly2 - test - - - org.eclipse.jetty - jetty-servlet-tester - test - - - junit - junit - test - - - org.mockito - mockito-all - test - - - org.slf4j - slf4j-simple - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - - *, - com.sun.jersey.spi.container.servlet - - /oauth2 - org.opendaylight.aaa.sts.Activator - ${project.basedir}/META-INF - - - - - - - diff --git a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/Activator.java b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/Activator.java deleted file mode 100644 index 1bf4591d..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/Activator.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; -import com.google.common.collect.Lists; -import java.util.List; -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.aaa.api.ClaimAuth; -import org.opendaylight.aaa.api.ClientService; -import org.opendaylight.aaa.api.CredentialAuth; -import org.opendaylight.aaa.api.IdMService; -import org.opendaylight.aaa.api.TokenAuth; -import org.opendaylight.aaa.api.TokenStore; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.util.tracker.ServiceTracker; -import org.osgi.util.tracker.ServiceTrackerCustomizer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An activator for the secure token server to inject in a - * {@link CredentialAuth} implementation. - * - * @author liemmn - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -public class Activator extends DependencyActivatorBase { - - private static final Logger LOG = LoggerFactory.getLogger(Activator.class); - - // Definition of several methods called in the ServiceLocator through - // Reflection - private static final String AUTHENTICATION_SERVICE_REMOVED = "authenticationServiceRemoved"; - private static final String AUTHENTICATION_SERVICE_ADDED = "authenticationServiceAdded"; - private static final String TOKEN_STORE_REMOVED = "tokenStoreRemoved"; - private static final String TOKEN_STORE_ADDED = "tokenStoreAdded"; - private static final String TOKEN_AUTH_REMOVED = "tokenAuthRemoved"; - private static final String TOKEN_AUTH_ADDED = "tokenAuthAdded"; - private static final String CLAIM_AUTH_REMOVED = "claimAuthRemoved"; - private static final String CLAIM_AUTH_ADDED = "claimAuthAdded"; - private static final String CREDENTIAL_AUTH_REMOVED = "credentialAuthRemoved"; - private static final String CREDENTIAL_AUTH_ADDED = "credentialAuthAdded"; - - // A collection of all services, which is used for closing ServiceTrackers - private ImmutableList> services; - - @Override - public void init(BundleContext context, DependencyManager manager) throws Exception { - - LOG.info("STS Activator initializing"); - manager.add(createComponent().setImplementation(ServiceLocator.getInstance()) - .add(createServiceDependency().setService(CredentialAuth.class) - .setRequired(true) - .setCallbacks( - CREDENTIAL_AUTH_ADDED, - CREDENTIAL_AUTH_REMOVED)) - .add(createServiceDependency().setService(ClaimAuth.class) - .setRequired(false) - .setCallbacks(CLAIM_AUTH_ADDED, - CLAIM_AUTH_REMOVED)) - .add(createServiceDependency().setService(TokenAuth.class) - .setRequired(false) - .setCallbacks(TOKEN_AUTH_ADDED, - TOKEN_AUTH_REMOVED)) - .add(createServiceDependency().setService(TokenStore.class) - .setRequired(true) - .setCallbacks(TOKEN_STORE_ADDED, - TOKEN_STORE_REMOVED)) - .add(createServiceDependency().setService(TokenStore.class) - .setRequired(true)) - .add(createServiceDependency().setService( - AuthenticationService.class) - .setRequired(true) - .setCallbacks( - AUTHENTICATION_SERVICE_ADDED, - AUTHENTICATION_SERVICE_REMOVED)) - .add(createServiceDependency().setService(IdMService.class) - .setRequired(true)) - .add(createServiceDependency().setService(ClientService.class) - .setRequired(true))); - - final Builder> servicesBuilder = new ImmutableList.Builder>(); - - // Async ServiceTrackers to track and load AAA STS bundles - final ServiceTracker authenticationService = new ServiceTracker<>( - context, AuthenticationService.class, - new AAAServiceTrackerCustomizer( - new Function() { - @Override - public Void apply(AuthenticationService authenticationService) { - ServiceLocator.getInstance().setAuthenticationService( - authenticationService); - return null; - } - })); - servicesBuilder.add(authenticationService); - authenticationService.open(); - - final ServiceTracker idmService = new ServiceTracker<>(context, - IdMService.class, new AAAServiceTrackerCustomizer( - new Function() { - @Override - public Void apply(IdMService idmService) { - ServiceLocator.getInstance().setIdmService(idmService); - return null; - } - })); - servicesBuilder.add(idmService); - idmService.open(); - - final ServiceTracker tokenAuthService = new ServiceTracker<>(context, - TokenAuth.class, new AAAServiceTrackerCustomizer( - new Function() { - @Override - public Void apply(TokenAuth tokenAuth) { - final List tokenAuthCollection = (List) Lists.newArrayList(tokenAuth); - ServiceLocator.getInstance().setTokenAuthCollection( - tokenAuthCollection); - return null; - } - })); - servicesBuilder.add(tokenAuthService); - tokenAuthService.open(); - - final ServiceTracker tokenStoreService = new ServiceTracker<>( - context, TokenStore.class, new AAAServiceTrackerCustomizer( - new Function() { - @Override - public Void apply(TokenStore tokenStore) { - ServiceLocator.getInstance().setTokenStore(tokenStore); - return null; - } - })); - servicesBuilder.add(tokenStoreService); - tokenStoreService.open(); - - final ServiceTracker clientService = new ServiceTracker<>( - context, ClientService.class, new AAAServiceTrackerCustomizer( - new Function() { - @Override - public Void apply(ClientService clientService) { - ServiceLocator.getInstance().setClientService(clientService); - return null; - } - })); - servicesBuilder.add(clientService); - clientService.open(); - - services = servicesBuilder.build(); - - LOG.info("STS Activator initialized; ServiceTracker may still be processing"); - } - - /** - * Wrapper for AAA generic service loading. - * - * @param - */ - static final class AAAServiceTrackerCustomizer implements ServiceTrackerCustomizer { - - private Function callback; - - public AAAServiceTrackerCustomizer(final Function callback) { - this.callback = callback; - } - - @Override - public S addingService(ServiceReference reference) { - S service = reference.getBundle().getBundleContext().getService(reference); - LOG.info("Unable to resolve {}", service.getClass()); - try { - callback.apply(service); - } catch (Exception e) { - LOG.error("Unable to resolve {}", service.getClass(), e); - } - return service; - } - - @Override - public void modifiedService(ServiceReference reference, S service) { - } - - @Override - public void removedService(ServiceReference reference, S service) { - } - } - - @Override - public void destroy(BundleContext context, DependencyManager manager) throws Exception { - - for (ServiceTracker serviceTracker : services) { - serviceTracker.close(); - } - } -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousPasswordValidator.java b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousPasswordValidator.java deleted file mode 100644 index 55b5b61f..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousPasswordValidator.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import javax.servlet.http.HttpServletRequest; -import org.apache.oltu.oauth2.common.OAuth; -import org.apache.oltu.oauth2.common.validators.AbstractValidator; - -/** - * A password validator that does not enforce client identification. - * - * @author liemmn - * - */ -public class AnonymousPasswordValidator extends AbstractValidator { - - public AnonymousPasswordValidator() { - requiredParams.add(OAuth.OAUTH_GRANT_TYPE); - requiredParams.add(OAuth.OAUTH_USERNAME); - requiredParams.add(OAuth.OAUTH_PASSWORD); - - enforceClientAuthentication = false; - } -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousRefreshTokenValidator.java b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousRefreshTokenValidator.java deleted file mode 100644 index 5b50c7da..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousRefreshTokenValidator.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import javax.servlet.http.HttpServletRequest; -import org.apache.oltu.oauth2.common.OAuth; -import org.apache.oltu.oauth2.common.validators.AbstractValidator; - -/** - * A refresh token validator that does not enforce client identification. - * - * @author liemmn - * - */ -public class AnonymousRefreshTokenValidator extends AbstractValidator { - - public AnonymousRefreshTokenValidator() { - requiredParams.add(OAuth.OAUTH_GRANT_TYPE); - requiredParams.add(OAuth.OAUTH_REFRESH_TOKEN); - - enforceClientAuthentication = false; - } -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/OAuthRequest.java b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/OAuthRequest.java deleted file mode 100644 index 2a2b34b6..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/OAuthRequest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import javax.servlet.http.HttpServletRequest; -import org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest; -import org.apache.oltu.oauth2.as.validator.UnauthenticatedAuthorizationCodeValidator; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.types.GrantType; -import org.apache.oltu.oauth2.common.validators.OAuthValidator; - -/** - * OAuth request wrapper. - * - * @author liemmn - * - */ -public class OAuthRequest extends AbstractOAuthTokenRequest { - - public OAuthRequest(HttpServletRequest request) throws OAuthSystemException, - OAuthProblemException { - super(request); - } - - @Override - public OAuthValidator initValidator() throws OAuthProblemException, - OAuthSystemException { - validators.put(GrantType.PASSWORD.toString(), AnonymousPasswordValidator.class); - validators.put(GrantType.REFRESH_TOKEN.toString(), AnonymousRefreshTokenValidator.class); - validators.put(GrantType.AUTHORIZATION_CODE.toString(), - UnauthenticatedAuthorizationCodeValidator.class); - return super.initValidator(); - } - -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/ServiceLocator.java b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/ServiceLocator.java deleted file mode 100644 index 2c1f84c3..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/ServiceLocator.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import java.util.List; -import java.util.Vector; -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.aaa.api.ClientService; -import org.opendaylight.aaa.api.CredentialAuth; -import org.opendaylight.aaa.api.IdMService; -import org.opendaylight.aaa.api.PasswordCredentials; -import org.opendaylight.aaa.api.TokenAuth; -import org.opendaylight.aaa.api.TokenStore; - -/** - * A service locator to bridge between the web world and OSGi world. - * - * @author liemmn - * - */ -public class ServiceLocator { - - private static final ServiceLocator instance = new ServiceLocator(); - - protected volatile List tokenAuthCollection = new Vector<>(); - - protected volatile CredentialAuth credentialAuth; - - protected volatile TokenStore tokenStore; - - protected volatile AuthenticationService authenticationService; - - protected volatile IdMService idmService; - - protected volatile ClientService clientService; - - private ServiceLocator() { - } - - public static ServiceLocator getInstance() { - return instance; - } - - /** - * Called through reflection by the sts activator. - * - * @see org.opendaylight.aaa.sts.Activator - * @param ta - */ - protected void tokenAuthAdded(TokenAuth ta) { - this.tokenAuthCollection.add(ta); - } - - /** - * Called through reflection by the sts activator. - * - * @see org.opendaylight.aaa.sts.Activator - * @param ta - */ - protected void tokenAuthRemoved(TokenAuth ta) { - this.tokenAuthCollection.remove(ta); - } - - protected void tokenStoreAdded(TokenStore ts) { - this.tokenStore = ts; - } - - protected void tokenStoreRemoved(TokenStore ts) { - this.tokenStore = null; - } - - protected void authenticationServiceAdded(AuthenticationService as) { - this.authenticationService = as; - } - - protected void authenticationServiceRemoved(AuthenticationService as) { - this.authenticationService = null; - } - - protected void credentialAuthAdded(CredentialAuth da) { - this.credentialAuth = da; - } - - protected void credentialAuthAddedRemoved(CredentialAuth da) { - this.credentialAuth = null; - } - - public List getTokenAuthCollection() { - return tokenAuthCollection; - } - - public void setTokenAuthCollection(List tokenAuthCollection) { - this.tokenAuthCollection = tokenAuthCollection; - } - - public CredentialAuth getCredentialAuth() { - return credentialAuth; - } - - public synchronized void setCredentialAuth(CredentialAuth credentialAuth) { - this.credentialAuth = credentialAuth; - } - - public TokenStore getTokenStore() { - return tokenStore; - } - - public void setTokenStore(TokenStore tokenStore) { - this.tokenStore = tokenStore; - } - - public AuthenticationService getAuthenticationService() { - return authenticationService; - } - - public void setAuthenticationService(AuthenticationService authenticationService) { - this.authenticationService = authenticationService; - } - - public IdMService getIdmService() { - return idmService; - } - - public void setIdmService(IdMService idmService) { - this.idmService = idmService; - } - - public ClientService getClientService() { - return clientService; - } - - public void setClientService(ClientService clientService) { - this.clientService = clientService; - } -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenAuthFilter.java b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenAuthFilter.java deleted file mode 100644 index 3fa7a66c..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenAuthFilter.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import com.sun.jersey.spi.container.ContainerRequest; -import com.sun.jersey.spi.container.ContainerRequestFilter; -import java.util.List; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.types.ParameterStyle; -import org.apache.oltu.oauth2.rs.request.OAuthAccessResourceRequest; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.AuthenticationException; -import org.opendaylight.aaa.api.TokenAuth; - -/** - * A token-based authentication filter for resource providers. - * - * Deprecated: Use AAAFilter instead. - * - * @author liemmn - * - */ -@Deprecated -public class TokenAuthFilter implements ContainerRequestFilter { - - private final String OPTIONS = "OPTIONS"; - private final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; - private final String AUTHORIZATION = "authorization"; - - @Context - private HttpServletRequest httpRequest; - - @Override - public ContainerRequest filter(ContainerRequest request) { - - // Do the CORS check first - if (checkCORSOptionRequest(request)) { - return request; - } - - // Are we up yet? - if (ServiceLocator.getInstance().getAuthenticationService() == null) { - throw new WebApplicationException( - Response.status(Status.SERVICE_UNAVAILABLE).type(MediaType.APPLICATION_JSON) - .entity("{\"error\":\"Authentication service unavailable\"}").build()); - } - - // Are we doing authentication or not? - if (ServiceLocator.getInstance().getAuthenticationService().isAuthEnabled()) { - Map> headers = request.getRequestHeaders(); - - // Go through and invoke other TokenAuth first... - List tokenAuthCollection = ServiceLocator.getInstance() - .getTokenAuthCollection(); - for (TokenAuth ta : tokenAuthCollection) { - try { - Authentication auth = ta.validate(headers); - if (auth != null) { - ServiceLocator.getInstance().getAuthenticationService().set(auth); - return request; - } - } catch (AuthenticationException ae) { - throw unauthorized(); - } - } - - // OK, last chance to validate token... - try { - OAuthAccessResourceRequest or = new OAuthAccessResourceRequest(httpRequest, - ParameterStyle.HEADER); - validate(or.getAccessToken()); - } catch (OAuthSystemException | OAuthProblemException e) { - throw unauthorized(); - } - } - - return request; - } - - /** - * CORS access control : when browser sends cross-origin request, it first - * sends the OPTIONS method with a list of access control request headers, - * which has a list of custom headers and access control method such as GET. - * POST etc. You custom header "Authorization will not be present in request - * header, instead it will be present as a value inside - * Access-Control-Request-Headers. We should not do any authorization - * against such request. for more details : - * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS - */ - - private boolean checkCORSOptionRequest(ContainerRequest request) { - if (OPTIONS.equals(request.getMethod())) { - List headerList = request.getRequestHeader(ACCESS_CONTROL_REQUEST_HEADERS); - if (headerList != null && !headerList.isEmpty()) { - String header = headerList.get(0); - if (header != null && header.toLowerCase().contains(AUTHORIZATION)) { - return true; - } - } - } - return false; - } - - // Validate an ODL token... - private Authentication validate(final String token) { - Authentication auth = ServiceLocator.getInstance().getTokenStore().get(token); - if (auth == null) { - throw unauthorized(); - } else { - ServiceLocator.getInstance().getAuthenticationService().set(auth); - } - return auth; - } - - // Houston, we got a problem! - private static final WebApplicationException unauthorized() { - ServiceLocator.getInstance().getAuthenticationService().clear(); - return new UnauthorizedException(); - } - - // A custom 401 web exception that handles http basic response as well - static final class UnauthorizedException extends WebApplicationException { - private static final long serialVersionUID = -1732363804773027793L; - static final String WWW_AUTHENTICATE = "WWW-Authenticate"; - static final Object OPENDAYLIGHT = "Basic realm=\"opendaylight\""; - private static final Response response = Response.status(Status.UNAUTHORIZED) - .header(WWW_AUTHENTICATE, OPENDAYLIGHT) - .build(); - - public UnauthorizedException() { - super(response); - } - } -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenEndpoint.java b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenEndpoint.java deleted file mode 100644 index a456d702..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenEndpoint.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static javax.servlet.http.HttpServletResponse.SC_CREATED; -import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; -import static javax.servlet.http.HttpServletResponse.SC_NOT_IMPLEMENTED; -import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; -import static javax.servlet.http.HttpServletResponse.SC_OK; -import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.List; -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; -import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; -import org.apache.oltu.oauth2.as.issuer.UUIDValueGenerator; -import org.apache.oltu.oauth2.as.response.OAuthASResponse; -import org.apache.oltu.oauth2.common.OAuth; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.OAuthResponse; -import org.apache.oltu.oauth2.common.message.types.GrantType; -import org.apache.oltu.oauth2.common.message.types.TokenType; -import org.opendaylight.aaa.AuthenticationBuilder; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.PasswordCredentialBuilder; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.AuthenticationException; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.api.PasswordCredentials; - -/** - * Secure Token Service (STS) endpoint. - * - * @author liemmn - * - */ -public class TokenEndpoint extends HttpServlet { - private static final long serialVersionUID = 8272453849539659999L; - - private static final String DOMAIN_SCOPE_REQUIRED = "Domain scope required"; - private static final String NOT_IMPLEMENTED = "not_implemented"; - private static final String UNAUTHORIZED = "unauthorized"; - - static final String TOKEN_GRANT_ENDPOINT = "/token"; - static final String TOKEN_REVOKE_ENDPOINT = "/revoke"; - static final String TOKEN_VALIDATE_ENDPOINT = "/validate"; - - private transient OAuthIssuer oi; - - @Override - public void init(ServletConfig config) throws ServletException { - oi = new OAuthIssuerImpl(new UUIDValueGenerator()); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { - try { - if (req.getServletPath().equals(TOKEN_GRANT_ENDPOINT)) { - createAccessToken(req, resp); - } else if (req.getServletPath().equals(TOKEN_REVOKE_ENDPOINT)) { - deleteAccessToken(req, resp); - } else if (req.getServletPath().equals(TOKEN_VALIDATE_ENDPOINT)) { - validateToken(req, resp); - } - } catch (AuthenticationException e) { - error(resp, SC_UNAUTHORIZED, e.getMessage()); - } catch (OAuthProblemException oe) { - error(resp, oe); - } catch (Exception e) { - error(resp, e); - } - } - - private void validateToken(HttpServletRequest req, HttpServletResponse resp) - throws IOException, OAuthSystemException { - String token = req.getReader().readLine(); - if (token != null) { - Authentication authn = ServiceLocator.getInstance().getTokenStore().get(token.trim()); - if (authn == null) { - throw new AuthenticationException(UNAUTHORIZED); - } else { - ServiceLocator.getInstance().getAuthenticationService().set(authn); - resp.setStatus(SC_OK); - } - } else { - throw new AuthenticationException(UNAUTHORIZED); - } - } - - // Delete an access token - private void deleteAccessToken(HttpServletRequest req, HttpServletResponse resp) - throws IOException { - String token = req.getReader().readLine(); - if (token != null) { - if (ServiceLocator.getInstance().getTokenStore().delete(token.trim())) { - resp.setStatus(SC_NO_CONTENT); - } else { - throw new AuthenticationException(UNAUTHORIZED); - } - } else { - throw new AuthenticationException(UNAUTHORIZED); - } - } - - // Create an access token - private void createAccessToken(HttpServletRequest req, HttpServletResponse resp) - throws OAuthSystemException, OAuthProblemException, IOException { - Claim claim = null; - String clientId = null; - - OAuthRequest oauthRequest = new OAuthRequest(req); - // Any client credentials? - clientId = oauthRequest.getClientId(); - if (clientId != null) { - ServiceLocator.getInstance().getClientService() - .validate(clientId, oauthRequest.getClientSecret()); - } - - // Credential request... - if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.PASSWORD.toString())) { - String domain = oauthRequest.getScopes().iterator().next(); - PasswordCredentials pc = new PasswordCredentialBuilder().setUserName( - oauthRequest.getUsername()).setPassword(oauthRequest.getPassword()) - .setDomain(domain).build(); - if (!oauthRequest.getScopes().isEmpty()) { - claim = ServiceLocator.getInstance().getCredentialAuth().authenticate(pc); - } - } else if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals( - GrantType.REFRESH_TOKEN.toString())) { - // Refresh token... - String token = oauthRequest.getRefreshToken(); - if (!oauthRequest.getScopes().isEmpty()) { - String domain = oauthRequest.getScopes().iterator().next(); - // Authenticate... - Authentication auth = ServiceLocator.getInstance().getTokenStore().get(token); - if (auth != null && domain != null) { - List roles = ServiceLocator.getInstance().getIdmService() - .listRoles(auth.userId(), domain); - if (!roles.isEmpty()) { - ClaimBuilder cb = new ClaimBuilder(auth); - cb.setDomain(domain); // scope domain - // Add roles for the scoped domain - for (String role : roles) { - cb.addRole(role); - } - claim = cb.build(); - } - } - } else { - error(resp, SC_BAD_REQUEST, DOMAIN_SCOPE_REQUIRED); - } - } else { - // Support authorization code later... - error(resp, SC_NOT_IMPLEMENTED, NOT_IMPLEMENTED); - } - - // Respond with OAuth token - oauthAccessTokenResponse(resp, claim, clientId); - } - - // Build OAuth access token response from the given claim - private void oauthAccessTokenResponse(HttpServletResponse resp, Claim claim, String clientId) - throws OAuthSystemException, IOException { - if (claim == null) { - throw new AuthenticationException(UNAUTHORIZED); - } - String token = oi.accessToken(); - - // Cache this token... - Authentication auth = new AuthenticationBuilder(new ClaimBuilder(claim).setClientId( - clientId).build()).setExpiration(tokenExpiration()).build(); - ServiceLocator.getInstance().getTokenStore().put(token, auth); - - OAuthResponse r = OAuthASResponse.tokenResponse(SC_CREATED).setAccessToken(token) - .setTokenType(TokenType.BEARER.toString()) - .setExpiresIn(Long.toString(auth.expiration())) - .buildJSONMessage(); - write(resp, r); - } - - // Token expiration - private long tokenExpiration() { - return ServiceLocator.getInstance().getTokenStore().tokenExpiration(); - } - - // Emit an error OAuthResponse with the given HTTP code - private void error(HttpServletResponse resp, int httpCode, String error) { - try { - OAuthResponse r = OAuthResponse.errorResponse(httpCode).setError(error) - .buildJSONMessage(); - write(resp, r); - } catch (Exception e1) { - // Nothing to do here - } - } - - // Emit an error OAuthResponse for the given OAuth-related exception - private void error(HttpServletResponse resp, OAuthProblemException e) { - try { - OAuthResponse r = OAuthResponse.errorResponse(SC_BAD_REQUEST).error(e) - .buildJSONMessage(); - write(resp, r); - } catch (Exception e1) { - // Nothing to do here - } - } - - // Emit an error OAuthResponse for the given generic exception - private void error(HttpServletResponse resp, Exception e) { - try { - OAuthResponse r = OAuthResponse.errorResponse(SC_INTERNAL_SERVER_ERROR) - .setError(e.getClass().getName()) - .setErrorDescription(e.getMessage()).buildJSONMessage(); - write(resp, r); - } catch (Exception e1) { - // Nothing to do here - } - } - - // Write out an OAuthResponse - private void write(HttpServletResponse resp, OAuthResponse r) throws IOException { - resp.setStatus(r.getResponseStatus()); - PrintWriter pw = resp.getWriter(); - pw.print(r.getBody()); - pw.flush(); - pw.close(); - } -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/main/resources/WEB-INF/web.xml b/odl-aaa-moon/aaa-authn-sts/src/main/resources/WEB-INF/web.xml deleted file mode 100644 index 83a9fa51..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/main/resources/WEB-INF/web.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - STS - org.opendaylight.aaa.sts.TokenEndpoint - 1 - - - STS - /token - - - STS - /revoke - - - STS - /validate - - diff --git a/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/RestFixture.java b/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/RestFixture.java deleted file mode 100644 index 0f806d91..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/RestFixture.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; - -/** - * Fixture for testing RESTful stuff. - * - * @author liemmn - * - */ -@Path("test") -public class RestFixture { - - @Context - private HttpServletRequest httpRequest; - - @GET - @Produces("text/plain") - public String msg() { - return "ok"; - } -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenAuthTest.java b/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenAuthTest.java deleted file mode 100644 index 7f888455..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenAuthTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.anyMap; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.UniformInterfaceException; -import com.sun.jersey.test.framework.JerseyTest; -import com.sun.jersey.test.framework.WebAppDescriptor; -import org.junit.BeforeClass; -import org.junit.Test; -import org.opendaylight.aaa.AuthenticationBuilder; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.aaa.api.TokenAuth; -import org.opendaylight.aaa.api.TokenStore; -import org.opendaylight.aaa.sts.TokenAuthFilter.UnauthorizedException; - -public class TokenAuthTest extends JerseyTest { - - private static final String RS_PACKAGES = "org.opendaylight.aaa.sts"; - private static final String JERSEY_FILTERS = "com.sun.jersey.spi.container.ContainerRequestFilters"; - private static final String AUTH_FILTERS = TokenAuthFilter.class.getName(); - - private static Authentication auth = new AuthenticationBuilder(new ClaimBuilder().setUserId( - "1234").setUser("Bob").addRole("admin").addRole("user").setDomain("tenantX").build()).setExpiration( - System.currentTimeMillis() + 1000).build(); - - private static final String GOOD_TOKEN = "9b01b7cf-8a49-346d-8c47-6a61193e2b60"; - private static final String BAD_TOKEN = "9b01b7cf-8a49-346d-8c47-6a611badbeef"; - - public TokenAuthTest() throws Exception { - super(new WebAppDescriptor.Builder(RS_PACKAGES).initParam(JERSEY_FILTERS, AUTH_FILTERS) - .build()); - } - - @BeforeClass - public static void init() { - ServiceLocator.getInstance().setAuthenticationService(mock(AuthenticationService.class)); - ServiceLocator.getInstance().setTokenStore(mock(TokenStore.class)); - when(ServiceLocator.getInstance().getTokenStore().get(GOOD_TOKEN)).thenReturn(auth); - when(ServiceLocator.getInstance().getTokenStore().get(BAD_TOKEN)).thenReturn(null); - when(ServiceLocator.getInstance().getAuthenticationService().isAuthEnabled()).thenReturn( - Boolean.TRUE); - } - - @Test() - public void testGetUnauthorized() { - try { - resource().path("test").get(String.class); - fail("Shoulda failed with 401!"); - } catch (UniformInterfaceException e) { - ClientResponse resp = e.getResponse(); - assertEquals(401, resp.getStatus()); - assertTrue(resp.getHeaders().get(UnauthorizedException.WWW_AUTHENTICATE) - .contains(UnauthorizedException.OPENDAYLIGHT)); - } - } - - @Test - public void testGet() { - String resp = resource().path("test").header("Authorization", "Bearer " + GOOD_TOKEN) - .get(String.class); - assertEquals("ok", resp); - } - - @SuppressWarnings("unchecked") - @Test - public void testGetWithValidator() { - try { - // Mock a laxed tokenauth... - TokenAuth ta = mock(TokenAuth.class); - when(ta.validate(anyMap())).thenReturn(auth); - ServiceLocator.getInstance().getTokenAuthCollection().add(ta); - testGet(); - } finally { - ServiceLocator.getInstance().getTokenAuthCollection().clear(); - } - } - -} diff --git a/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenEndpointTest.java b/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenEndpointTest.java deleted file mode 100644 index 06dd6302..00000000 --- a/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenEndpointTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.sts; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import org.eclipse.jetty.testing.HttpTester; -import org.eclipse.jetty.testing.ServletTester; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.opendaylight.aaa.AuthenticationBuilder; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.api.ClientService; -import org.opendaylight.aaa.api.CredentialAuth; -import org.opendaylight.aaa.api.IdMService; -import org.opendaylight.aaa.api.PasswordCredentials; -import org.opendaylight.aaa.api.TokenAuth; -import org.opendaylight.aaa.api.TokenStore; - -/** - * A unit test for token endpoint. - * - * @author liemmn - * - */ -public class TokenEndpointTest { - private static final long TOKEN_TIMEOUT_SECS = 10; - private static final String CONTEXT = "/oauth2"; - private static final String DIRECT_AUTH = "grant_type=password&username=admin&password=admin&scope=pepsi&client_id=dlux&client_secret=secrete"; - private static final String REFRESH_TOKEN = "grant_type=refresh_token&refresh_token=whateverisgood&scope=pepsi"; - - private static final Claim claim = new ClaimBuilder().setUser("bob").setUserId("1234") - .addRole("admin").build(); - private final static ServletTester server = new ServletTester(); - - @BeforeClass - public static void init() throws Exception { - // Set up server - server.setContextPath(CONTEXT); - - // Add our servlet under test - server.addServlet(TokenEndpoint.class, "/revoke"); - server.addServlet(TokenEndpoint.class, "/token"); - - // Let's do dis - server.start(); - } - - @AfterClass - public static void shutdown() throws Exception { - server.stop(); - } - - @Before - public void setup() { - mockServiceLocator(); - when(ServiceLocator.getInstance().getTokenStore().tokenExpiration()).thenReturn( - TOKEN_TIMEOUT_SECS); - } - - @After - public void teardown() { - ServiceLocator.getInstance().getTokenAuthCollection().clear(); - } - - @Test - public void testCreateToken401() throws Exception { - HttpTester req = new HttpTester(); - req.setMethod("POST"); - req.setHeader("Content-Type", "application/x-www-form-urlencoded"); - req.setContent(DIRECT_AUTH); - req.setURI(CONTEXT + TokenEndpoint.TOKEN_GRANT_ENDPOINT); - req.setVersion("HTTP/1.0"); - - HttpTester resp = new HttpTester(); - resp.parse(server.getResponses(req.generate())); - assertEquals(401, resp.getStatus()); - } - - @Test - public void testCreateTokenWithPassword() throws Exception { - when( - ServiceLocator.getInstance().getCredentialAuth() - .authenticate(any(PasswordCredentials.class))).thenReturn(claim); - - HttpTester req = new HttpTester(); - req.setMethod("POST"); - req.setHeader("Content-Type", "application/x-www-form-urlencoded"); - req.setContent(DIRECT_AUTH); - req.setURI(CONTEXT + TokenEndpoint.TOKEN_GRANT_ENDPOINT); - req.setVersion("HTTP/1.0"); - - HttpTester resp = new HttpTester(); - resp.parse(server.getResponses(req.generate())); - assertEquals(201, resp.getStatus()); - assertTrue(resp.getContent().contains("expires_in\":10")); - assertTrue(resp.getContent().contains("Bearer")); - } - - @Test - public void testCreateTokenWithRefreshToken() throws Exception { - when(ServiceLocator.getInstance().getTokenStore().get(anyString())).thenReturn( - new AuthenticationBuilder(claim).build()); - when(ServiceLocator.getInstance().getIdmService().listRoles(anyString(), anyString())).thenReturn( - Arrays.asList("admin", "user")); - - HttpTester req = new HttpTester(); - req.setMethod("POST"); - req.setHeader("Content-Type", "application/x-www-form-urlencoded"); - req.setContent(REFRESH_TOKEN); - req.setURI(CONTEXT + TokenEndpoint.TOKEN_GRANT_ENDPOINT); - req.setVersion("HTTP/1.0"); - - HttpTester resp = new HttpTester(); - resp.parse(server.getResponses(req.generate())); - assertEquals(201, resp.getStatus()); - assertTrue(resp.getContent().contains("expires_in\":10")); - assertTrue(resp.getContent().contains("Bearer")); - } - - @Test - public void testDeleteToken() throws Exception { - when(ServiceLocator.getInstance().getTokenStore().delete("token_to_be_deleted")).thenReturn( - true); - - HttpTester req = new HttpTester(); - req.setMethod("POST"); - req.setHeader("Content-Type", "application/x-www-form-urlencoded"); - req.setContent("token_to_be_deleted"); - req.setURI(CONTEXT + TokenEndpoint.TOKEN_REVOKE_ENDPOINT); - req.setVersion("HTTP/1.0"); - - HttpTester resp = new HttpTester(); - resp.parse(server.getResponses(req.generate())); - assertEquals(204, resp.getStatus()); - } - - @SuppressWarnings("unchecked") - private static void mockServiceLocator() { - ServiceLocator.getInstance().setClientService(mock(ClientService.class)); - ServiceLocator.getInstance().setIdmService(mock(IdMService.class)); - ServiceLocator.getInstance().setAuthenticationService(mock(AuthenticationService.class)); - ServiceLocator.getInstance().setTokenStore(mock(TokenStore.class)); - ServiceLocator.getInstance().setCredentialAuth(mock(CredentialAuth.class)); - ServiceLocator.getInstance().getTokenAuthCollection().add(mock(TokenAuth.class)); - } -} diff --git a/odl-aaa-moon/aaa-authn/pom.xml b/odl-aaa-moon/aaa-authn/pom.xml deleted file mode 100644 index 06027a60..00000000 --- a/odl-aaa-moon/aaa-authn/pom.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn - bundle - - - - org.opendaylight.aaa - aaa-authn-api - - - com.google.guava - guava - - - org.slf4j - slf4j-api - - - com.sun.jersey - jersey-server - provided - - - org.osgi - org.osgi.core - provided - - - org.osgi - org.osgi.compendium - provided - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - - junit - junit - test - - - org.slf4j - slf4j-simple - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.opendaylight.aaa.Activator - - ${project.basedir}/META-INF - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - package - - attach-artifact - - - - - ${project.build.directory}/classes/authn.cfg - cfg - config - - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java deleted file mode 100644 index cfe27ef0..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa; - -import java.util.Dictionary; -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.aaa.api.ClientService; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.service.cm.ManagedService; - -/** - * Activator to register {@link AuthenticationService} with OSGi. - * - * @author liemmn - * - */ -public class Activator extends DependencyActivatorBase { - - private static final String AUTHN_PID = "org.opendaylight.aaa.authn"; - - @Override - public void init(BundleContext context, DependencyManager manager) throws Exception { - manager.add(createComponent().setInterface( - new String[] { AuthenticationService.class.getName() }, null).setImplementation( - AuthenticationManager.instance())); - - ClientManager cm = new ClientManager(); - manager.add(createComponent().setInterface(new String[] { ClientService.class.getName() }, - null).setImplementation(cm)); - context.registerService(ManagedService.class.getName(), cm, addPid(ClientManager.defaults)); - context.registerService(ManagedService.class.getName(), AuthenticationManager.instance(), - addPid(AuthenticationManager.defaults)); - } - - @Override - public void destroy(BundleContext context, DependencyManager manager) throws Exception { - } - - private Dictionary addPid(Dictionary dict) { - dict.put(Constants.SERVICE_PID, AUTHN_PID); - return dict; - } -} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java deleted file mode 100644 index 948cbac6..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa; - -import static org.opendaylight.aaa.EqualUtil.areEqual; -import static org.opendaylight.aaa.HashCodeUtil.hash; - -import java.io.Serializable; -import java.util.Set; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.Claim; - -/** - * A builder for the authentication context. - * - * The expiration defaults to 0. - * - * @author liemmn - * - */ -public class AuthenticationBuilder { - - private long expiration = 0L; - private Claim claim; - - public AuthenticationBuilder(Claim claim) { - this.claim = claim; - } - - public AuthenticationBuilder setExpiration(long expiration) { - this.expiration = expiration; - return this; - } - - public Authentication build() { - return new ImmutableAuthentication(this); - } - - private static final class ImmutableAuthentication implements Authentication, Serializable { - private static final long serialVersionUID = 4919078164955609987L; - private int hashCode = 0; - long expiration = 0L; - Claim claim; - - private ImmutableAuthentication(AuthenticationBuilder base) { - if (base.claim == null) { - throw new IllegalStateException("The Claim is null."); - } - claim = new ClaimBuilder(base.claim).build(); - expiration = base.expiration; - - if (base.expiration < 0) { - throw new IllegalStateException("The expiration is less than 0."); - } - } - - @Override - public long expiration() { - return expiration; - } - - @Override - public String clientId() { - return claim.clientId(); - } - - @Override - public String userId() { - return claim.userId(); - } - - @Override - public String user() { - return claim.user(); - } - - @Override - public String domain() { - return claim.domain(); - } - - @Override - public Set roles() { - return claim.roles(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Authentication)) { - return false; - } - Authentication a = (Authentication) o; - return areEqual(expiration, a.expiration()) && areEqual(claim.roles(), a.roles()) - && areEqual(claim.domain(), a.domain()) && areEqual(claim.userId(), a.userId()) - && areEqual(claim.user(), a.user()) && areEqual(claim.clientId(), a.clientId()); - } - - @Override - public int hashCode() { - if (hashCode == 0) { - int result = HashCodeUtil.SEED; - result = hash(result, expiration); - result = hash(result, claim.hashCode()); - hashCode = result; - } - return hashCode; - } - - @Override - public String toString() { - return "expiration:" + expiration + "," + claim.toString(); - } - } -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java deleted file mode 100644 index 5f6420a3..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa; - -import java.util.Dictionary; -import java.util.Hashtable; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.AuthenticationService; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedService; - -/** - * An {@link InheritableThreadLocal}-based {@link AuthenticationService}. - * - * @author liemmn - */ -public class AuthenticationManager implements AuthenticationService, ManagedService { - private static final String AUTH_ENABLED_ERR = "Error setting authEnabled"; - - static final String AUTH_ENABLED = "authEnabled"; - static final Dictionary defaults = new Hashtable<>(); - static { - defaults.put(AUTH_ENABLED, Boolean.FALSE.toString()); - } - - // In non-Karaf environments, authEnabled is set to false by default - private static volatile boolean authEnabled = false; - - private final static AuthenticationManager am = new AuthenticationManager(); - private final ThreadLocal auth = new InheritableThreadLocal<>(); - - private AuthenticationManager() { - } - - static AuthenticationManager instance() { - return am; - } - - @Override - public Authentication get() { - return auth.get(); - } - - @Override - public void set(Authentication a) { - auth.set(a); - } - - @Override - public void clear() { - auth.remove(); - } - - @Override - public boolean isAuthEnabled() { - return authEnabled; - } - - @Override - public void updated(Dictionary properties) throws ConfigurationException { - if (properties == null) { - return; - } - - String propertyValue = (String) properties.get(AUTH_ENABLED); - boolean isTrueString = Boolean.parseBoolean(propertyValue); - if (!isTrueString && !"false".equalsIgnoreCase(propertyValue)) { - throw new ConfigurationException(AUTH_ENABLED, AUTH_ENABLED_ERR); - } - authEnabled = isTrueString; - } -} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java deleted file mode 100644 index 4e4a8ef3..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa; - -import static org.opendaylight.aaa.EqualUtil.areEqual; -import static org.opendaylight.aaa.HashCodeUtil.hash; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableSet; -import java.io.Serializable; -import java.util.LinkedHashSet; -import java.util.Set; -import org.opendaylight.aaa.api.Claim; - -/** - * Builder for a {@link Claim}. The userId, user, and roles information is - * mandatory. - * - * @author liemmn - * - */ -public class ClaimBuilder { - private String userId = ""; - private String user = ""; - private Set roles = new LinkedHashSet<>(); - private String clientId = ""; - private String domain = ""; - - public ClaimBuilder() { - } - - public ClaimBuilder(Claim claim) { - clientId = claim.clientId(); - userId = claim.userId(); - user = claim.user(); - domain = claim.domain(); - roles.addAll(claim.roles()); - } - - public ClaimBuilder setClientId(String clientId) { - this.clientId = Strings.nullToEmpty(clientId).trim(); - return this; - } - - public ClaimBuilder setUserId(String userId) { - this.userId = Strings.nullToEmpty(userId).trim(); - return this; - } - - public ClaimBuilder setUser(String userName) { - user = Strings.nullToEmpty(userName).trim(); - return this; - } - - public ClaimBuilder setDomain(String domain) { - this.domain = Strings.nullToEmpty(domain).trim(); - return this; - } - - public ClaimBuilder addRoles(Set roles) { - for (String role : roles) { - addRole(role); - } - return this; - } - - public ClaimBuilder addRole(String role) { - roles.add(Strings.nullToEmpty(role).trim()); - return this; - } - - public Claim build() { - return new ImmutableClaim(this); - } - - protected static class ImmutableClaim implements Claim, Serializable { - private static final long serialVersionUID = -8115027645190209129L; - private int hashCode = 0; - protected String clientId; - protected String userId; - protected String user; - protected String domain; - protected ImmutableSet roles; - - protected ImmutableClaim(ClaimBuilder base) { - clientId = base.clientId; - userId = base.userId; - user = base.user; - domain = base.domain; - roles = ImmutableSet. builder().addAll(base.roles).build(); - - if (userId.isEmpty() || user.isEmpty() || roles.isEmpty() || roles.contains("")) { - throw new IllegalStateException( - "The Claim is missing one or more of the required fields."); - } - } - - @Override - public String clientId() { - return clientId; - } - - @Override - public String userId() { - return userId; - } - - @Override - public String user() { - return user; - } - - @Override - public String domain() { - return domain; - } - - @Override - public Set roles() { - return roles; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof Claim)) - return false; - Claim a = (Claim) o; - return areEqual(roles, a.roles()) && areEqual(domain, a.domain()) - && areEqual(userId, a.userId()) && areEqual(user, a.user()) - && areEqual(clientId, a.clientId()); - } - - @Override - public int hashCode() { - if (hashCode == 0) { - int result = HashCodeUtil.SEED; - result = hash(result, clientId); - result = hash(result, userId); - result = hash(result, user); - result = hash(result, domain); - result = hash(result, roles); - hashCode = result; - } - return hashCode; - } - - @Override - public String toString() { - return "clientId:" + clientId + "," + "userId:" + userId + "," + "userName:" + user - + "," + "domain:" + domain + "," + "roles:" + roles; - } - } -} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java deleted file mode 100644 index e7e51424..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa; - -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import org.apache.felix.dm.Component; -import org.opendaylight.aaa.api.AuthenticationException; -import org.opendaylight.aaa.api.ClientService; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedService; - -/** - * A configuration-based client manager. - * - * @author liemmn - * - */ -public class ClientManager implements ClientService, ManagedService { - static final String CLIENTS = "authorizedClients"; - private static final String CLIENTS_FORMAT_ERR = "Clients are space-delimited in the form of :"; - private static final String UNAUTHORIZED_CLIENT_ERR = "Unauthorized client"; - - // Defaults (needed only for non-Karaf deployments) - static final Dictionary defaults = new Hashtable<>(); - static { - defaults.put(CLIENTS, "dlux:secrete"); - } - - private final Map clients = new ConcurrentHashMap<>(); - - // This should be a singleton - ClientManager() { - } - - // Called by DM when all required dependencies are satisfied. - void init(Component c) throws ConfigurationException { - reconfig(defaults); - } - - @Override - public void validate(String clientId, String clientSecret) throws AuthenticationException { - // TODO: Post-Helium, we will support a CRUD API - if (!clients.containsKey(clientId)) { - throw new AuthenticationException(UNAUTHORIZED_CLIENT_ERR); - } - if (!clients.get(clientId).equals(clientSecret)) { - throw new AuthenticationException(UNAUTHORIZED_CLIENT_ERR); - } - } - - @Override - public void updated(Dictionary props) throws ConfigurationException { - if (props == null) { - props = defaults; - } - reconfig(props); - } - - // Reconfigure the client map... - private void reconfig(@SuppressWarnings("rawtypes") Dictionary props) - throws ConfigurationException { - try { - String authorizedClients = (String) props.get(CLIENTS); - Map newClients = new HashMap<>(); - if (authorizedClients != null) { - for (String client : authorizedClients.split(" ")) { - String[] aClient = client.split(":"); - newClients.put(aClient[0], aClient[1]); - } - } - clients.clear(); - clients.putAll(newClients); - } catch (Throwable t) { - throw new ConfigurationException(null, CLIENTS_FORMAT_ERR); - } - } - -} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java deleted file mode 100644 index 17204d0e..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa; - -/** - * Simple class to aide in implementing equals. - *

- * - * Arrays are not handled by this class. This is because the - * Arrays.equals methods should be used for array fields. - */ -public final class EqualUtil { - static public boolean areEqual(boolean aThis, boolean aThat) { - return aThis == aThat; - } - - static public boolean areEqual(char aThis, char aThat) { - return aThis == aThat; - } - - static public boolean areEqual(long aThis, long aThat) { - return aThis == aThat; - } - - static public boolean areEqual(float aThis, float aThat) { - return Float.floatToIntBits(aThis) == Float.floatToIntBits(aThat); - } - - static public boolean areEqual(double aThis, double aThat) { - return Double.doubleToLongBits(aThis) == Double.doubleToLongBits(aThat); - } - - static public boolean areEqual(Object aThis, Object aThat) { - return aThis == null ? aThat == null : aThis.equals(aThat); - } -} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java deleted file mode 100644 index c295b3ed..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java +++ /dev/null @@ -1,104 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - *****************************************************************************/ - -package org.opendaylight.aaa; - -import java.lang.reflect.Array; - -/** - * Collected methods which allow easy implementation of hashCode. - * - * Example use case: - * - *

- * public int hashCode() {
- *     int result = HashCodeUtil.SEED;
- *     // collect the contributions of various fields
- *     result = HashCodeUtil.hash(result, fPrimitive);
- *     result = HashCodeUtil.hash(result, fObject);
- *     result = HashCodeUtil.hash(result, fArray);
- *     return result;
- * }
- * 
- */ -public final class HashCodeUtil { - - /** - * An initial value for a hashCode, to which is added contributions - * from fields. Using a non-zero value decreases collisions of - * hashCode values. - */ - public static final int SEED = 23; - - /** booleans. */ - public static int hash(int aSeed, boolean aBoolean) { - return firstTerm(aSeed) + (aBoolean ? 1 : 0); - } - - /*** chars. */ - public static int hash(int aSeed, char aChar) { - return firstTerm(aSeed) + aChar; - } - - /** ints. */ - public static int hash(int aSeed, int aInt) { - return firstTerm(aSeed) + aInt; - } - - /** longs. */ - public static int hash(int aSeed, long aLong) { - return firstTerm(aSeed) + (int) (aLong ^ (aLong >>> 32)); - } - - /** floats. */ - public static int hash(int aSeed, float aFloat) { - return hash(aSeed, Float.floatToIntBits(aFloat)); - } - - /** doubles. */ - public static int hash(int aSeed, double aDouble) { - return hash(aSeed, Double.doubleToLongBits(aDouble)); - } - - /** - * aObject is a possibly-null object field, and possibly an array. - * - * If aObject is an array, then each element may be a primitive or - * a possibly-null object. - */ - public static int hash(int aSeed, Object aObject) { - int result = aSeed; - if (aObject == null) { - result = hash(result, 0); - } else if (!isArray(aObject)) { - result = hash(result, aObject.hashCode()); - } else { - int length = Array.getLength(aObject); - for (int idx = 0; idx < length; ++idx) { - Object item = Array.get(aObject, idx); - // if an item in the array references the array itself, prevent - // infinite looping - if (!(item == aObject)) { - result = hash(result, item); - } - } - } - return result; - } - - // PRIVATE - private static final int fODD_PRIME_NUMBER = 37; - - private static int firstTerm(int aSeed) { - return fODD_PRIME_NUMBER * aSeed; - } - - private static boolean isArray(Object aObject) { - return aObject.getClass().isArray(); - } -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java deleted file mode 100644 index d8a2e87a..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa; - -import static org.opendaylight.aaa.EqualUtil.areEqual; -import static org.opendaylight.aaa.HashCodeUtil.hash; - -import org.opendaylight.aaa.api.PasswordCredentials; - -/** - * {@link PasswordCredentials} builder. - * - * @author liemmn - * - */ -public class PasswordCredentialBuilder { - private final MutablePasswordCredentials pc = new MutablePasswordCredentials(); - - public PasswordCredentialBuilder setUserName(String username) { - pc.username = username; - return this; - } - - public PasswordCredentialBuilder setPassword(String password) { - pc.password = password; - return this; - } - - public PasswordCredentialBuilder setDomain(String domain) { - pc.domain = domain; - return this; - } - - public PasswordCredentials build() { - return pc; - } - - private static class MutablePasswordCredentials implements PasswordCredentials { - private int hashCode = 0; - private String username; - private String password; - private String domain; - - @Override - public String username() { - return username; - } - - @Override - public String password() { - return password; - } - - @Override - public String domain() { - return domain; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof PasswordCredentials)) { - return false; - } - PasswordCredentials p = (PasswordCredentials) o; - return areEqual(username, p.username()) && areEqual(password, p.password()); - } - - @Override - public int hashCode() { - if (hashCode == 0) { - int result = HashCodeUtil.SEED; - result = hash(result, username); - result = hash(result, password); - hashCode = result; - } - return hashCode; - } - } -} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java deleted file mode 100644 index 3ded52da..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; -import org.opendaylight.aaa.api.Authentication; - -/** - * A {@link BlockingQueue} decorator with injected security context. - * - * @author liemmn - * - * @param - * queue element type - */ -public class SecureBlockingQueue implements BlockingQueue { - private final BlockingQueue> queue; - - /** - * Constructor. - * - * @param queue - * blocking queue implementation to use - */ - public SecureBlockingQueue(BlockingQueue> queue) { - this.queue = queue; - } - - @Override - public T remove() { - return setAuth(queue.remove()); - } - - @Override - public T poll() { - return setAuth(queue.poll()); - } - - @Override - public T element() { - return setAuth(queue.element()); - } - - @Override - public T peek() { - return setAuth(queue.peek()); - } - - @Override - public int size() { - return queue.size(); - } - - @Override - public boolean isEmpty() { - return queue.isEmpty(); - } - - @Override - public Iterator iterator() { - return new Iterator() { - Iterator> it = queue.iterator(); - - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public T next() { - return it.next().data; - } - - @Override - public void remove() { - it.remove(); - } - }; - } - - @Override - public Object[] toArray() { - return toData().toArray(); - } - - @SuppressWarnings("hiding") - @Override - public T[] toArray(T[] a) { - return toData().toArray(a); - } - - @Override - public boolean containsAll(Collection c) { - return toData().containsAll(c); - } - - @Override - public boolean addAll(Collection c) { - return queue.addAll(fromData(c)); - } - - @Override - public boolean removeAll(Collection c) { - return queue.removeAll(fromData(c)); - } - - @Override - public boolean retainAll(Collection c) { - return queue.retainAll(fromData(c)); - } - - @Override - public void clear() { - queue.clear(); - } - - @Override - public boolean add(T e) { - return queue.add(new SecureData<>(e)); - } - - @Override - public boolean offer(T e) { - return queue.offer(new SecureData<>(e)); - } - - @Override - public void put(T e) throws InterruptedException { - queue.put(new SecureData(e)); - } - - @Override - public boolean offer(T e, long timeout, TimeUnit unit) throws InterruptedException { - return queue.offer(new SecureData<>(e), timeout, unit); - } - - @Override - public T take() throws InterruptedException { - return setAuth(queue.take()); - } - - @Override - public T poll(long timeout, TimeUnit unit) throws InterruptedException { - return setAuth(queue.poll(timeout, unit)); - } - - @Override - public int remainingCapacity() { - return queue.remainingCapacity(); - } - - @Override - public boolean remove(Object o) { - Iterator> it = queue.iterator(); - while (it.hasNext()) { - SecureData sd = it.next(); - if (sd.data.equals(o)) { - return queue.remove(sd); - } - } - return false; - } - - @Override - public boolean contains(Object o) { - Iterator> it = queue.iterator(); - while (it.hasNext()) { - SecureData sd = it.next(); - if (sd.data.equals(o)) { - return true; - } - } - return false; - } - - @Override - public int drainTo(Collection c) { - Collection> sd = new ArrayList<>(); - int n = queue.drainTo(sd); - c.addAll(toData(sd)); - return n; - } - - @Override - public int drainTo(Collection c, int maxElements) { - Collection> sd = new ArrayList<>(); - int n = queue.drainTo(sd, maxElements); - c.addAll(toData(sd)); - return n; - } - - // Rehydrate security context - private T setAuth(SecureData i) { - AuthenticationManager.instance().set(i.auth); - return i.data; - } - - // Construct secure data collection from a plain old data collection - @SuppressWarnings("unchecked") - private Collection> fromData(Collection c) { - Collection> sd = new ArrayList<>(c.size()); - for (Object d : c) { - sd.add((SecureData) new SecureData<>(d)); - } - return sd; - } - - // Extract the data portion out from the secure data - @SuppressWarnings("unchecked") - private Collection toData() { - return toData(Arrays.> asList(queue.toArray(new SecureData[0]))); - } - - // Extract the data portion out from the secure data - private Collection toData(Collection> secureData) { - Collection data = new ArrayList<>(secureData.size()); - Iterator> it = secureData.iterator(); - while (it.hasNext()) { - data.add(it.next().data); - } - return data; - } - - // Inject security context - public static final class SecureData { - private final T data; - private final Authentication auth; - - private SecureData(T data) { - this.data = data; - this.auth = AuthenticationManager.instance().get(); - } - - @SuppressWarnings("rawtypes") - @Override - public boolean equals(Object o) { - if (o == null) { - return false; - } - return (o instanceof SecureData) ? data.equals(((SecureData) o).data) : false; - } - - @Override - public int hashCode() { - return data.hashCode(); - } - } -} diff --git a/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties b/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties deleted file mode 100644 index 75537f6b..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties +++ /dev/null @@ -1,12 +0,0 @@ -org.opendaylight.aaa.authn.name = Opendaylight AAA Authentication Configuration -org.opendaylight.aaa.authn.description = Configuration for AAA authorized clients -org.opendaylight.aaa.authn.authorizedClients.name = Authorized Clients -org.opendaylight.aaa.authn.authorizedClients.description = Space-delimited list of authorized \ - clients, with client id and client password separated by a ':'. \ - Example: dlux:secrete -org.opendaylight.aaa.authn.authEnabled.name = Enable authentication -org.opendaylight.aaa.authn.authEnabled.description = Enable authentication by setting it \ -to the value 'true', or 'false' if bypassing authentication. \ -Note that bypassing authentication may result in your controller being more \ -vulnerable to unauthorized accesses. Authorization, if enabled, will not work if \ -authentication is disabled. \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml b/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml deleted file mode 100644 index 10150587..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg b/odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg deleted file mode 100644 index e7326f86..00000000 --- a/odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg +++ /dev/null @@ -1,2 +0,0 @@ -authorizedClients=dlux:secrete -authEnabled=true \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java deleted file mode 100644 index 2f69fe5b..00000000 --- a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; -import org.junit.Test; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.Claim; - -public class AuthenticationBuilderTest { - private Set roles = new LinkedHashSet<>(Arrays.asList("role1", "role2")); - private Claim validClaim = new ClaimBuilder().setDomain("aName").setUserId("1") - .setClientId("2222").setUser("bob").addRole("foo").addRoles(roles).build(); - - @Test - public void testBuildWithExpiration() { - Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); - assertEquals(1, a1.expiration()); - assertEquals("aName", a1.domain()); - assertEquals("1", a1.userId()); - assertEquals("2222", a1.clientId()); - assertEquals("bob", a1.user()); - assertTrue(a1.roles().contains("foo")); - assertTrue(a1.roles().containsAll(roles)); - assertEquals(3, a1.roles().size()); - Authentication a2 = new AuthenticationBuilder(a1).build(); - assertNotEquals(a1, a2); - Authentication a3 = new AuthenticationBuilder(a1).setExpiration(1).build(); - assertEquals(a1, a3); - } - - @Test - public void testBuildWithoutExpiration() { - Authentication a1 = new AuthenticationBuilder(validClaim).build(); - assertEquals(0, a1.expiration()); - assertEquals("aName", a1.domain()); - assertEquals("1", a1.userId()); - assertEquals("2222", a1.clientId()); - assertEquals("bob", a1.user()); - assertTrue(a1.roles().contains("foo")); - assertTrue(a1.roles().containsAll(roles)); - assertEquals(3, a1.roles().size()); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithNegativeExpiration() { - AuthenticationBuilder a1 = new AuthenticationBuilder(validClaim).setExpiration(-1); - a1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithNullClaim() { - AuthenticationBuilder a1 = new AuthenticationBuilder(null); - a1.build(); - } - - @Test - public void testToString() { - Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); - assertEquals( - "expiration:1,clientId:2222,userId:1,userName:bob,domain:aName,roles:[foo, role1, role2]", - a1.toString()); - } - - @Test - public void testEquals() { - Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); - assertTrue(a1.equals(a1)); - Authentication a2 = new AuthenticationBuilder(a1).setExpiration(1).build(); - assertTrue(a1.equals(a2)); - assertTrue(a2.equals(a1)); - Authentication a3 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); - assertTrue(a1.equals(a3)); - assertTrue(a3.equals(a2)); - assertTrue(a1.equals(a2)); - } - - @Test - public void testNotEquals() { - Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); - assertFalse(a1.equals(null)); - assertFalse(a1.equals("wrong object")); - Authentication a2 = new AuthenticationBuilder(a1).build(); - assertFalse(a1.equals(a2)); - assertFalse(a2.equals(a1)); - Authentication a3 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); - assertFalse(a1.equals(a2)); - assertTrue(a1.equals(a3)); - assertFalse(a2.equals(a3)); - Authentication a4 = new AuthenticationBuilder(validClaim).setExpiration(9).build(); - assertFalse(a1.equals(a4)); - assertFalse(a4.equals(a1)); - Authentication a5 = new AuthenticationBuilder(a1).setExpiration(9).build(); - assertFalse(a1.equals(a5)); - assertFalse(a5.equals(a1)); - } - - @Test - public void testHashCode() { - Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); - assertEquals(a1.hashCode(), a1.hashCode()); - Authentication a2 = new AuthenticationBuilder(a1).setExpiration(1).build(); - assertTrue(a1.equals(a2)); - assertEquals(a1.hashCode(), a2.hashCode()); - Authentication a3 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); - assertTrue(a1.equals(a3)); - assertEquals(a1.hashCode(), a3.hashCode()); - assertEquals(a2.hashCode(), a3.hashCode()); - Authentication a4 = new AuthenticationBuilder(a1).setExpiration(9).build(); - assertFalse(a1.equals(a4)); - assertNotEquals(a1.hashCode(), a4.hashCode()); - Authentication a5 = new AuthenticationBuilder(a1).build(); - assertFalse(a1.equals(a5)); - assertNotEquals(a1.hashCode(), a5.hashCode()); - } -} diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java deleted file mode 100644 index 540df287..00000000 --- a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import org.junit.Test; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.AuthenticationService; -import org.osgi.service.cm.ConfigurationException; - -public class AuthenticationManagerTest { - @Test - public void testAuthenticationCrudSameThread() { - Authentication auth = new AuthenticationBuilder(new ClaimBuilder().setUser("Bob") - .setUserId("1234").addRole("admin").addRole("guest").build()).build(); - AuthenticationService as = AuthenticationManager.instance(); - - assertNotNull(as); - - as.set(auth); - assertEquals(auth, as.get()); - - as.clear(); - assertNull(as.get()); - } - - @Test - public void testAuthenticationCrudSpawnedThread() throws InterruptedException, - ExecutionException { - AuthenticationService as = AuthenticationManager.instance(); - Authentication auth = new AuthenticationBuilder(new ClaimBuilder().setUser("Bob") - .setUserId("1234").addRole("admin").addRole("guest").build()).build(); - - as.set(auth); - Future f = Executors.newSingleThreadExecutor().submit(new Worker()); - assertEquals(auth, f.get()); - - as.clear(); - f = Executors.newSingleThreadExecutor().submit(new Worker()); - assertNull(f.get()); - } - - @Test - public void testAuthenticationCrudSpawnedThreadPool() throws InterruptedException, - ExecutionException { - AuthenticationService as = AuthenticationManager.instance(); - Authentication auth = new AuthenticationBuilder(new ClaimBuilder().setUser("Bob") - .setUserId("1234").addRole("admin").addRole("guest").build()).build(); - - as.set(auth); - List> fs = Executors.newFixedThreadPool(2).invokeAll( - Arrays.asList(new Worker(), new Worker())); - for (Future f : fs) { - assertEquals(auth, f.get()); - } - - as.clear(); - fs = Executors.newFixedThreadPool(2).invokeAll(Arrays.asList(new Worker(), new Worker())); - for (Future f : fs) { - assertNull(f.get()); - } - } - - @Test - public void testUpdatedValid() throws ConfigurationException { - Dictionary props = new Hashtable<>(); - AuthenticationManager as = AuthenticationManager.instance(); - - assertFalse(as.isAuthEnabled()); - - props.put(AuthenticationManager.AUTH_ENABLED, "TrUe"); - as.updated(props); - assertTrue(as.isAuthEnabled()); - - props.put(AuthenticationManager.AUTH_ENABLED, "FaLsE"); - as.updated(props); - assertFalse(as.isAuthEnabled()); - } - - @Test - public void testUpdatedNullProperty() throws ConfigurationException { - AuthenticationManager as = AuthenticationManager.instance(); - - assertFalse(as.isAuthEnabled()); - as.updated(null); - assertFalse(as.isAuthEnabled()); - } - - @Test(expected = ConfigurationException.class) - public void testUpdatedInvalidValue() throws ConfigurationException { - AuthenticationManager as = AuthenticationManager.instance(); - Dictionary props = new Hashtable<>(); - - props.put(AuthenticationManager.AUTH_ENABLED, "yes"); - as.updated(props); - } - - @Test(expected = ConfigurationException.class) - public void testUpdatedInvalidKey() throws ConfigurationException { - AuthenticationManager as = AuthenticationManager.instance(); - Dictionary props = new Hashtable<>(); - - props.put("Invalid Key", "true"); - as.updated(props); - } - - private class Worker implements Callable { - @Override - public Authentication call() throws Exception { - AuthenticationService as = AuthenticationManager.instance(); - return as.get(); - } - } -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java deleted file mode 100644 index 372eb6d2..00000000 --- a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.HashSet; -import org.junit.Test; -import org.opendaylight.aaa.api.Claim; - -/** - * - * @author liemmn - * - */ -public class ClaimBuilderTest { - @Test - public void testBuildWithAll() { - Claim c1 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") - .setUserId("1234").addRole("foo").addRole("foo2") - .addRoles(new HashSet<>(Arrays.asList("foo", "bar"))).build(); - assertEquals("dlux", c1.clientId()); - assertEquals("pepsi", c1.domain()); - assertEquals("john", c1.user()); - assertEquals("1234", c1.userId()); - assertTrue(c1.roles().contains("foo")); - assertTrue(c1.roles().contains("foo2")); - assertTrue(c1.roles().contains("bar")); - assertEquals(3, c1.roles().size()); - Claim c2 = new ClaimBuilder(c1).build(); - assertEquals(c1, c2); - } - - @Test - public void testBuildWithRequired() { - Claim c1 = new ClaimBuilder().setUser("john").setUserId("1234").addRole("foo").build(); - assertEquals("john", c1.user()); - assertEquals("1234", c1.userId()); - assertTrue(c1.roles().contains("foo")); - assertEquals(1, c1.roles().size()); - assertEquals("", c1.domain()); - assertEquals("", c1.clientId()); - } - - @Test - public void testBuildWithEmptyOptional() { - Claim c1 = new ClaimBuilder().setDomain(" ").setClientId(" ").setUser("john") - .setUserId("1234").addRole("foo").build(); - assertEquals("", c1.domain()); - assertEquals("", c1.clientId()); - assertEquals("john", c1.user()); - assertEquals("1234", c1.userId()); - assertTrue(c1.roles().contains("foo")); - assertEquals(1, c1.roles().size()); - } - - @Test - public void testBuildWithNullOptional() { - Claim c1 = new ClaimBuilder().setDomain(null).setClientId(null).setUser("john") - .setUserId("1234").addRole("foo").build(); - assertEquals("", c1.domain()); - assertEquals("", c1.clientId()); - assertEquals("john", c1.user()); - assertEquals("1234", c1.userId()); - assertTrue(c1.roles().contains("foo")); - assertEquals(1, c1.roles().size()); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithDefault() { - ClaimBuilder c1 = new ClaimBuilder(); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithoutUser() { - ClaimBuilder c1 = new ClaimBuilder().setUserId("1234").addRole("foo"); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithNullUser() { - ClaimBuilder c1 = new ClaimBuilder().setUser(null).setUserId("1234").addRole("foo"); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithEmptyUser() { - ClaimBuilder c1 = new ClaimBuilder().setUser(" ").setUserId("1234").addRole("foo"); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithoutUserId() { - ClaimBuilder c1 = new ClaimBuilder().setUser("john").addRole("foo"); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithNullUserId() { - ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId(null).addRole("foo"); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithEmptyUserId() { - ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId(" ").addRole("foo"); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithoutRole() { - ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId("1234"); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithNullRole() { - ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId("1234").addRole(null); - c1.build(); - } - - @Test(expected = IllegalStateException.class) - public void testBuildWithEmptyRole() { - ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId("1234").addRole(" "); - c1.build(); - } - - @Test - public void testEquals() { - Claim c1 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") - .setUserId("1234").addRole("foo").build(); - assertTrue(c1.equals(c1)); - Claim c2 = new ClaimBuilder(c1).addRole("foo").build(); - assertTrue(c1.equals(c2)); - assertTrue(c2.equals(c1)); - Claim c3 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") - .setUserId("1234").addRole("foo").build(); - assertTrue(c1.equals(c3)); - assertTrue(c3.equals(c2)); - assertTrue(c1.equals(c2)); - } - - @Test - public void testNotEquals() { - Claim c1 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") - .setUserId("1234").addRole("foo").build(); - assertFalse(c1.equals(null)); - assertFalse(c1.equals("wrong object")); - Claim c2 = new ClaimBuilder(c1).addRoles(new HashSet<>(Arrays.asList("foo", "bar"))) - .build(); - assertEquals(2, c2.roles().size()); - assertFalse(c1.equals(c2)); - assertFalse(c2.equals(c1)); - Claim c3 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") - .setUserId("1234").addRole("foo").build(); - assertFalse(c1.equals(c2)); - assertTrue(c1.equals(c3)); - assertFalse(c2.equals(c3)); - Claim c5 = new ClaimBuilder().setUser("john").setUserId("1234").addRole("foo").build(); - assertFalse(c1.equals(c5)); - assertFalse(c5.equals(c1)); - } - - @Test - public void testHash() { - Claim c1 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") - .setUserId("1234").addRole("foo").build(); - assertEquals(c1.hashCode(), c1.hashCode()); - Claim c2 = new ClaimBuilder(c1).addRole("foo").build(); - assertTrue(c1.equals(c2)); - assertEquals(c1.hashCode(), c2.hashCode()); - Claim c3 = new ClaimBuilder(c1).addRoles(new HashSet<>(Arrays.asList("foo", "bar"))) - .build(); - assertFalse(c1.equals(c3)); - assertNotEquals(c1.hashCode(), c3.hashCode()); - Claim c4 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") - .setUserId("1234").addRole("foo").build(); - assertTrue(c1.equals(c4)); - assertEquals(c1.hashCode(), c4.hashCode()); - assertEquals(c2.hashCode(), c4.hashCode()); - Claim c5 = new ClaimBuilder().setUser("john").setUserId("1234").addRole("foo").build(); - assertFalse(c1.equals(c5)); - assertNotEquals(c1.hashCode(), c5.hashCode()); - } - - @Test - public void testToString() { - Claim c1 = new ClaimBuilder().setUser("john").setUserId("1234").addRole("foo").build(); - assertEquals("clientId:,userId:1234,userName:john,domain:,roles:[foo]", c1.toString()); - c1 = new ClaimBuilder(c1).setClientId("dlux").setDomain("pepsi").build(); - assertEquals("clientId:dlux,userId:1234,userName:john,domain:pepsi,roles:[foo]", - c1.toString()); - c1 = new ClaimBuilder(c1).addRole("bar").build(); - assertEquals("clientId:dlux,userId:1234,userName:john,domain:pepsi,roles:[foo, bar]", - c1.toString()); - } -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java deleted file mode 100644 index 059ba9a3..00000000 --- a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa; - -import static org.junit.Assert.fail; - -import java.util.Dictionary; -import java.util.Hashtable; -import org.junit.Before; -import org.junit.Test; -import org.opendaylight.aaa.api.AuthenticationException; -import org.osgi.service.cm.ConfigurationException; - -/** - * - * @author liemmn - * - */ -public class ClientManagerTest { - private static final ClientManager cm = new ClientManager(); - - @Before - public void setup() throws ConfigurationException { - cm.init(null); - } - - @Test - public void testValidate() { - cm.validate("dlux", "secrete"); - } - - @Test(expected = AuthenticationException.class) - public void testFailValidate() { - cm.validate("dlux", "what?"); - } - - @Test - public void testUpdate() throws ConfigurationException { - Dictionary configs = new Hashtable<>(); - configs.put(ClientManager.CLIENTS, "aws:amazon dlux:xxx"); - cm.updated(configs); - cm.validate("aws", "amazon"); - cm.validate("dlux", "xxx"); - } - - @Test - public void testFailUpdate() { - Dictionary configs = new Hashtable<>(); - configs.put(ClientManager.CLIENTS, "aws:amazon dlux"); - try { - cm.updated(configs); - fail("Shoulda failed updating bad configuration"); - } catch (ConfigurationException ce) { - // Expected - } - cm.validate("dlux", "secrete"); - try { - cm.validate("aws", "amazon"); - fail("Shoulda failed updating bad configuration"); - } catch (AuthenticationException ae) { - // Expected - } - } -} diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java deleted file mode 100644 index 2dabb77b..00000000 --- a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa; - -import static org.junit.Assert.assertEquals; - -import java.util.HashSet; -import org.junit.Test; -import org.opendaylight.aaa.api.PasswordCredentials; - -public class PasswordCredentialTest { - - @Test - public void testBuilder() { - PasswordCredentials pc1 = new PasswordCredentialBuilder().setUserName("bob") - .setPassword("secrete").build(); - assertEquals("bob", pc1.username()); - assertEquals("secrete", pc1.password()); - - PasswordCredentials pc2 = new PasswordCredentialBuilder().setUserName("bob") - .setPassword("secrete").build(); - assertEquals(pc1, pc2); - - PasswordCredentials pc3 = new PasswordCredentialBuilder().setUserName("bob") - .setPassword("secret").build(); - HashSet pcs = new HashSet<>(); - pcs.add(pc1); - pcs.add(pc2); - pcs.add(pc3); - assertEquals(2, pcs.size()); - } - -} diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java deleted file mode 100644 index 16627d9f..00000000 --- a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.opendaylight.aaa.SecureBlockingQueue.SecureData; -import org.opendaylight.aaa.api.Authentication; - -public class SecureBlockingQueueTest { - private final int MAX_TASKS = 100; - - @Before - public void setup() { - AuthenticationManager.instance().clear(); - } - - @Test - public void testSecureThreadPoolExecutor() throws InterruptedException, ExecutionException { - BlockingQueue queue = new SecureBlockingQueue<>( - new ArrayBlockingQueue>(10)); - ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 500, TimeUnit.MILLISECONDS, - queue); - executor.prestartAllCoreThreads(); - for (int cnt = 1; cnt <= MAX_TASKS; cnt++) { - assertEquals(Integer.toString(cnt), - executor.submit(new Task(Integer.toString(cnt), "1111", "user")).get().user()); - } - executor.shutdown(); - } - - @Test - public void testNormalThreadPoolExecutor() throws InterruptedException, ExecutionException { - BlockingQueue queue = new ArrayBlockingQueue(10); - ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 500, TimeUnit.MILLISECONDS, - queue); - executor.prestartAllCoreThreads(); - for (int cnt = 1; cnt <= MAX_TASKS; cnt++) { - assertNull(executor.submit(new Task(Integer.toString(cnt), "1111", "user")).get()); - } - executor.shutdown(); - } - - @Test - public void testQueueOps() throws InterruptedException, ExecutionException { - BlockingQueue queue = new SecureBlockingQueue<>( - new ArrayBlockingQueue>(3)); - ExecutorService es = Executors.newFixedThreadPool(3); - es.submit(new Producer("foo", "1111", "user", queue)).get(); - assertEquals(1, queue.size()); - assertEquals("foo", es.submit(new Consumer(queue)).get()); - es.submit(new Producer("bar", "2222", "user", queue)).get(); - assertEquals("bar", queue.peek()); - assertEquals("bar", queue.element()); - assertEquals(1, queue.size()); - assertEquals("bar", queue.poll()); - assertTrue(queue.isEmpty()); - es.shutdown(); - } - - @Test - public void testCollectionOps() throws InterruptedException, ExecutionException { - BlockingQueue queue = new SecureBlockingQueue<>( - new ArrayBlockingQueue>(6)); - for (int i = 1; i <= 3; i++) - queue.add("User" + i); - Iterator it = queue.iterator(); - while (it.hasNext()) - assertTrue(it.next().startsWith("User")); - assertEquals(3, queue.toArray().length); - List actual = Arrays.asList(queue.toArray(new String[0])); - assertEquals("User1", actual.iterator().next()); - assertTrue(queue.containsAll(actual)); - queue.addAll(actual); - assertEquals(6, queue.size()); - queue.retainAll(Arrays.asList(new String[] { "User2" })); - assertEquals(2, queue.size()); - assertEquals("User2", queue.iterator().next()); - queue.removeAll(actual); - assertTrue(queue.isEmpty()); - queue.add("hello"); - assertEquals(1, queue.size()); - queue.clear(); - assertTrue(queue.isEmpty()); - } - - @Test - public void testBlockingQueueOps() throws InterruptedException { - BlockingQueue queue = new SecureBlockingQueue<>( - new ArrayBlockingQueue>(3)); - queue.offer("foo"); - assertEquals(1, queue.size()); - queue.offer("bar", 500, TimeUnit.MILLISECONDS); - assertEquals(2, queue.size()); - assertEquals("foo", queue.poll()); - assertTrue(queue.contains("bar")); - queue.remove("bar"); - assertEquals(3, queue.remainingCapacity()); - queue.addAll(Arrays.asList(new String[] { "foo", "bar", "tom" })); - assertEquals(3, queue.size()); - assertEquals("foo", queue.poll(500, TimeUnit.MILLISECONDS)); - assertEquals(2, queue.size()); - List drain = new LinkedList<>(); - queue.drainTo(drain); - assertTrue(queue.isEmpty()); - assertEquals(2, drain.size()); - queue.addAll(Arrays.asList(new String[] { "foo", "bar", "tom" })); - drain.clear(); - queue.drainTo(drain, 1); - assertEquals(2, queue.size()); - assertEquals(1, drain.size()); - } - - // Task to run in a ThreadPoolExecutor - private class Task implements Callable { - Task(String name, String userId, String role) { - // Mock that each task has its original authentication context - AuthenticationManager.instance().set( - new AuthenticationBuilder(new ClaimBuilder().setUser(name).setUserId(userId) - .addRole(role).build()).build()); - } - - @Override - public Authentication call() throws Exception { - return AuthenticationManager.instance().get(); - } - } - - // Producer sets auth context - private class Producer implements Callable { - private final String name; - private final String userId; - private final String role; - private final BlockingQueue queue; - - Producer(String name, String userId, String role, BlockingQueue queue) { - this.name = name; - this.userId = userId; - this.role = role; - this.queue = queue; - } - - @Override - public String call() throws InterruptedException { - AuthenticationManager.instance().set( - new AuthenticationBuilder(new ClaimBuilder().setUser(name).setUserId(userId) - .addRole(role).build()).build()); - queue.put(name); - return name; - } - } - - // Consumer gets producer's auth context via data element in queue - private class Consumer implements Callable { - private final BlockingQueue queue; - - Consumer(BlockingQueue queue) { - this.queue = queue; - } - - @Override - public String call() { - queue.remove(); - Authentication auth = AuthenticationManager.instance().get(); - return (auth == null) ? null : auth.user(); - } - } - -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-config/pom.xml b/odl-aaa-moon/aaa-authz/aaa-authz-config/pom.xml deleted file mode 100644 index 4e19ed42..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-config/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../../parent - - - authz-service-config - AuthZ Service Configuration files - jar - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - - attach-artifact - - package - - - - ${project.build.directory}/classes/initial/${config.authz.service.configfile} - xml - config - - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-config/src/main/resources/initial/08-authz-config.xml b/odl-aaa-moon/aaa-authz/aaa-authz-config/src/main/resources/initial/08-authz-config.xml deleted file mode 100644 index 5b59ca20..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-config/src/main/resources/initial/08-authz-config.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - authz:aaa-authz-service - aaa-authz-service - - - dom:dom-broker-osgi-registry - dom-broker - - - - binding:binding-data-broker - binding-data-broker - - - - RestConfService - Any - * - admin - - - - - - - - dom:dom-broker-osgi-registry - - authz-connector-default - - /modules/module[type='aaa-authz-service'][name='aaa-authz-service'] - - - - - - - - - - - urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv?module=aaa-authz-service-impl&revision=2014-07-01 - - - diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml b/odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml deleted file mode 100644 index a1d3a28f..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - 4.0.0 - - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../../parent - - - aaa-authz-model - ${project.artifactId} - - - - org.opendaylight.mdsal - yang-binding - - - org.opendaylight.mdsal.model - ietf-inet-types - - - org.opendaylight.mdsal.model - ietf-yang-types - - - org.opendaylight.mdsal.model - yang-ext - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.apache.maven.plugins - maven-javadoc-plugin - - maven - - - - - aggregate - - site - - - - - org.opendaylight.yangtools - yang-maven-plugin - ${yangtools.version} - - - - generate-sources - - - src/main/yang - - - - org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl - - ${salGeneratorPath} - - - true - - - - - - - org.opendaylight.mdsal - maven-sal-api-gen-plugin - ${yangtools.version} - jar - - - - - - bundle - - diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-model/src/main/yang/authorization-schema.yang b/odl-aaa-moon/aaa-authz/aaa-authz-model/src/main/yang/authorization-schema.yang deleted file mode 100644 index 2e0cf9cb..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-model/src/main/yang/authorization-schema.yang +++ /dev/null @@ -1,190 +0,0 @@ -module authorization-schema { - yang-version 1; - namespace "urn:aaa:yang:authz:ds"; - prefix "authz"; - organization "TBD"; - - contact "wdec@cisco.com"; - - revision 2014-07-22 { - description - "Initial revision."; - } - - //Main module begins - - //TODO: Refactor service type as URI - - //Define the servicetype; Service is used to identify the requestors' name, which would correspond to an ODL component eg Restconf. Possibly - //the naming will derive from the OSGi bundle name of the AuthZ requesting party. - - typedef service-type { - type string; - } - - //Resource denotes the actual resource that is the subject of the AuthZ request. - - typedef resource-type { - type string; - default "*"; - - //Examples of resources: - //Data : /operational/opendaylight-inventory:nodes/node/openflow:1/node-connector/openflow:1:1 - //Wildcarded data: /operational/opendaylight-inventory:nodes/node/*/node-connector/* - //RPC: /operations/example-ops:reboot - //Wildcarded RPC: /operations/example-ops:* - //Notification: /notifications/example-ops:startup - } - - //Role denotes the normalized role that is attributed to the AuthZ requestor, eg "admin" - - typedef role-type { - type string; - } - - //Domain denotes the customer domain that is the attributed of the AuthZ requestor, eg cisco.com - - typedef domain-type { - type string; - } - - //Action denotes the requested AuthZ action on the resource - //TODO: Refactor as identities to allow for augmentation. - - typedef action-type { - type enumeration { - enum put; - enum commit; - enum exists; - enum getIdentifier; - enum read; - enum cancel; - enum submit; - enum delete; - enum merge; - enum any; - } - default "any"; - } - - typedef authorization-response-type { - type enumeration { - enum not-authorized { value 0; } - enum authorized { value 1; } - } - } - - typedef authorization-duration-type { - type uint32; - } - - // Following grouping is the core AuthZ policy permissions data-structure, dual keyed by service and action. - // Permissions will be set-up per application. NOTE: Group and role can be equivalent. do we need both? - - grouping authorization-grp { - list policies { - key "service"; - leaf service { - type service-type; - } - leaf action { - type action-type; - } - leaf resource { - type resource-type; - mandatory true; - } - leaf role { - type role-type; - mandatory true; - } - leaf authorization { - type authorization-response-type; - } - } - } - - // Following container provides the simple, non-domain specific AuthZ policy data-structure, dual keyed by service and action. - - container simple-authorization { - uses authorization-grp; - } - - // Following container provides the domain AuthZ policy data-structure. Each Policy is extended with a authz-domain-chain, - // which contains a prioritized list of the leafrefs to additional domain policies that also apply to this domain. - // The construct allows the chaining of policies like foo.com -> customer.sp.com -> customer.carrier.com. - - - container domain-authorization { - list domains { - key "domain-name"; - leaf domain-name { - type domain-type; - } - uses authorization-grp; - list authz-domain-chain { - key "priority"; - leaf priority { - type uint32; - } - leaf domain-name { - type leafref { - path "/additional-domain-authz/domains/domain-name"; - } - } - } - } -} - -container additional-domain-authz { - list domains { - key "domain-name"; - leaf domain-name { - type domain-type; - } - uses authorization-grp; - } - } - - - - /* The following is the AuthZ RPC definition */ - - rpc req-authorization { - description - "Check Authorization for a given combination of action and role. - A not-authorized will be returned if unsuccessful."; - - input { - leaf domain-name { - type domain-type; - } - leaf service { - type service-type; - } - leaf action { - type action-type; - mandatory true; - } - - leaf resource { - type resource-type; - mandatory true; - } - leaf role { - type role-type; - mandatory true; - } - - } - - output { - - leaf authorization-response { - type authorization-response-type; - mandatory true; - } - - } - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/pom.xml b/odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/pom.xml deleted file mode 100644 index 95db7458..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../../parent - - - authz-restconf-config - - AuthZ Restconf Connector Configuration file - jar - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - - attach-artifact - - package - - - - ${project.build.directory}/classes/initial/${config.restconf.configfile} - xml - config - - - - - - - - - diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/src/main/resources/initial/09-rest-connector.xml b/odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/src/main/resources/initial/09-rest-connector.xml deleted file mode 100644 index deba6558..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/src/main/resources/initial/09-rest-connector.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - rest:rest-connector-impl - rest-connector-default-impl - 8185 - - dom:dom-broker-osgi-registry - authz-connector-default - - - - - - - rest:rest-connector - - rest-connector-default - - /modules/module[type='rest-connector-impl'][name='rest-connector-default-impl'] - - - - - - - - - urn:opendaylight:params:xml:ns:yang:controller:md:sal:rest:connector?module=opendaylight-rest-connector&revision=2014-07-24 - - diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml b/odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml deleted file mode 100644 index a0afef82..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../../parent - - 4.0.0 - - aaa-authz-service - bundle - - - - org.opendaylight.controller - sal-binding-util - - - org.opendaylight.controller - sal-common-util - - - org.opendaylight.yangtools - yang-data-api - - - commons-codec - commons-codec - - - org.opendaylight.controller - sal-binding-api - - - org.opendaylight.controller - config-api - - - org.opendaylight.controller - sal-binding-config - - - org.opendaylight.aaa - aaa-authz-model - - - org.opendaylight.aaa - aaa-authn-api - - - org.opendaylight.controller - sal-core-api - - - org.opendaylight.controller - sal-core-spi - - - org.jboss.resteasy - jaxrs-api - provided - - - - - junit - junit - test - - - org.mockito - mockito-all - test - - - org.slf4j - slf4j-simple - test - - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - - org.opendaylight.aaa.config.yang.aaa_srv, - - - - - - org.opendaylight.yangtools - yang-maven-plugin - ${yangtools.version} - - - config - - generate-sources - - - - - - org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator - - ${jmxGeneratorPath} - - - urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang - - - - - org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl - ${salGeneratorPath} - - - true - - - - - - org.opendaylight.controller - yang-jmx-generator-plugin - ${config.version} - - - org.opendaylight.mdsal - maven-sal-api-gen-plugin - ${yangtools.version} - - - - - - - diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzBrokerImpl.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzBrokerImpl.java deleted file mode 100644 index d4ac79af..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzBrokerImpl.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import java.util.Collection; - -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; -import org.opendaylight.controller.sal.core.api.Broker; -import org.opendaylight.controller.sal.core.api.Consumer; -import org.opendaylight.controller.sal.core.api.Provider; -import org.osgi.framework.BundleContext; - -/** - * Created by wdec on 26/08/2014. - */ -public class AuthzBrokerImpl implements Broker, AutoCloseable, Provider { - - private Broker broker; - private ProviderSession providerSession; - private AuthenticationService authenticationService; - - public void setBroker(Broker broker) { - this.broker = broker; - } - - @Override - public void close() throws Exception { - - } - - // Implements AuthzBroker handling of registering consumers or providers. - @Override - public ConsumerSession registerConsumer(Consumer consumer) { - - ConsumerSession realSession = broker.registerConsumer(new ConsumerWrapper(consumer)); - AuthzConsumerContextImpl authzConsumerContext = new AuthzConsumerContextImpl(realSession, - this); - consumer.onSessionInitiated(authzConsumerContext); - return authzConsumerContext; - } - - @Override - public ConsumerSession registerConsumer(Consumer consumer, BundleContext bundleContext) { - - ConsumerSession realSession = broker.registerConsumer(new ConsumerWrapper(consumer), - bundleContext); - AuthzConsumerContextImpl authzConsumerContext = new AuthzConsumerContextImpl(realSession, - this); - consumer.onSessionInitiated(authzConsumerContext); - return authzConsumerContext; - } - - @Override - public ProviderSession registerProvider(Provider provider) { - - ProviderSession realSession = broker.registerProvider(new ProviderWrapper(provider)); - AuthzProviderContextImpl authzProviderContext = new AuthzProviderContextImpl(realSession, - this); - provider.onSessionInitiated(authzProviderContext); - return authzProviderContext; - } - - @Override - public ProviderSession registerProvider(Provider provider, BundleContext bundleContext) { - - // Allow the real broker to do its thing, while providing a wrapped - // callback - ProviderSession realSession = broker.registerProvider(new ProviderWrapper(provider), - bundleContext); - - // Create Authz ProviderContext - AuthzProviderContextImpl authzProviderContext = new AuthzProviderContextImpl(realSession, - this); - - // Run onsessionInitiated on injected provider with the AuthZ provider - // context. - provider.onSessionInitiated(authzProviderContext); - return authzProviderContext; - - } - - // Handle the AuthZBroker registration with the real broker - @Override - public void onSessionInitiated(ProviderSession providerSession) { - - // Get now the real DOMDataBroker and register it with the - // AuthzDOMBroker together with the provider session - final DOMDataBroker domDataBroker = providerSession.getService(DOMDataBroker.class); - AuthzDomDataBroker.getInstance().setProviderSession(providerSession); - AuthzDomDataBroker.getInstance().setDomDataBroker(domDataBroker); - AuthzDomDataBroker.getInstance().setAuthService(this.authenticationService); - } - - @Override - public Collection getProviderFunctionality() { - return null; - } - - public void setAuthenticationService(AuthenticationService authenticationService) { - this.authenticationService = authenticationService; - } - - // Wrapper for Provider - - public static class ProviderWrapper implements Provider { - private final Provider provider; - - public ProviderWrapper(Provider provider) { - this.provider = provider; - } - - @Override - public void onSessionInitiated(ProviderSession providerSession) { - // Do a Noop when the real broker calls back - } - - @Override - public Collection getProviderFunctionality() { - // Allow the RestconfImpl to respond to this - return provider.getProviderFunctionality(); - } - } - - // Wrapper for Consumer - public static class ConsumerWrapper implements Consumer { - - private final Consumer consumer; - - public ConsumerWrapper(Consumer consumer) { - this.consumer = consumer; - } - - @Override - public void onSessionInitiated(ConsumerSession consumerSession) { - // Do a Noop when the real broker calls back - } - - @Override - public Collection getConsumerFunctionality() { - return consumer.getConsumerFunctionality(); - } - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImpl.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImpl.java deleted file mode 100644 index 07ba51cd..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; -import org.opendaylight.controller.sal.core.api.Broker; -import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession; -import org.opendaylight.controller.sal.core.api.BrokerService; -import org.opendaylight.controller.sal.core.spi.ForwardingConsumerSession; - -/** - * Created by wdec on 28/08/2014. - */ -public class AuthzConsumerContextImpl extends ForwardingConsumerSession { - - private final Broker.ConsumerSession realSession; - - public AuthzConsumerContextImpl(Broker.ConsumerSession realSession, AuthzBrokerImpl authzBroker) { - this.realSession = realSession; - } - - @Override - protected ConsumerSession delegate() { - return realSession; - } - - @Override - public T getService(Class tClass) { - T t; - // Check for class and return Authz broker only for DOMBroker - if (tClass == DOMDataBroker.class) { - t = (T) AuthzDomDataBroker.getInstance(); - } else { - t = realSession.getService(tClass); - } - // AuthzDomDataBroker.getInstance().setDomDataBroker((DOMDataBroker)t); - return t; - } - -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDataReadWriteTransaction.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDataReadWriteTransaction.java deleted file mode 100644 index 4cc232bc..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDataReadWriteTransaction.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import com.google.common.base.Optional; -import com.google.common.util.concurrent.CheckedFuture; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; - -import org.opendaylight.controller.md.sal.common.api.TransactionStatus; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; -import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; -import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authz.ds.rev140722.ActionType; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; - -/** - * Created by wdec on 26/08/2014. - */ -public class AuthzDataReadWriteTransaction implements DOMDataReadWriteTransaction { - - private final DOMDataReadWriteTransaction domDataReadWriteTransaction; - - public AuthzDataReadWriteTransaction(DOMDataReadWriteTransaction domDataReadWriteTransaction) { - this.domDataReadWriteTransaction = domDataReadWriteTransaction; - } - - @Override - public boolean cancel() { - if (AuthzServiceImpl.isAuthorized(ActionType.Cancel)) { - return domDataReadWriteTransaction.cancel(); - } - return false; - } - - @Override - public void delete(LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Delete)) { - domDataReadWriteTransaction.delete(logicalDatastoreType, yangInstanceIdentifier); - } - } - - @Override - public CheckedFuture submit() { - if (AuthzServiceImpl.isAuthorized(ActionType.Submit)) { - return domDataReadWriteTransaction.submit(); - } - TransactionCommitFailedException e = new TransactionCommitFailedException( - "Unauthorized User"); - return Futures.immediateFailedCheckedFuture(e); - } - - @Deprecated - @Override - public ListenableFuture> commit() { - if (AuthzServiceImpl.isAuthorized(ActionType.Commit)) { - return domDataReadWriteTransaction.commit(); - } - TransactionCommitFailedException e = new TransactionCommitFailedException( - "Unauthorized User"); - return Futures.immediateFailedCheckedFuture(e); - } - - @Override - public CheckedFuture>, ReadFailedException> read( - LogicalDatastoreType logicalDatastoreType, YangInstanceIdentifier yangInstanceIdentifier) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Read)) { - return domDataReadWriteTransaction.read(logicalDatastoreType, yangInstanceIdentifier); - } - ReadFailedException e = new ReadFailedException("Authorization Failed"); - return Futures.immediateFailedCheckedFuture(e); - } - - @Override - public CheckedFuture exists( - LogicalDatastoreType logicalDatastoreType, YangInstanceIdentifier yangInstanceIdentifier) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Exists)) { - return domDataReadWriteTransaction.exists(logicalDatastoreType, yangInstanceIdentifier); - } - ReadFailedException e = new ReadFailedException("Authorization Failed"); - return Futures.immediateFailedCheckedFuture(e); - } - - @Override - public void put(LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode normalizedNode) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Put)) { - domDataReadWriteTransaction.put(logicalDatastoreType, yangInstanceIdentifier, - normalizedNode); - } - } - - @Override - public void merge(LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode normalizedNode) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Merge)) { - domDataReadWriteTransaction.merge(logicalDatastoreType, yangInstanceIdentifier, - normalizedNode); - } - } - - @Override - public Object getIdentifier() { - if (AuthzServiceImpl.isAuthorized(ActionType.GetIdentifier)) { - return domDataReadWriteTransaction.getIdentifier(); - } - return null; - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDomDataBroker.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDomDataBroker.java deleted file mode 100644 index 911f5a48..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDomDataBroker.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import java.util.Map; -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener; -import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; -import org.opendaylight.controller.md.sal.dom.api.DOMDataBrokerExtension; -import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener; -import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; -import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction; -import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; -import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain; -import org.opendaylight.controller.sal.core.api.Broker; -import org.opendaylight.controller.sal.core.api.BrokerService; -import org.opendaylight.yangtools.concepts.ListenerRegistration; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; - -/** - * Created by wdec on 26/08/2014. - */ -public class AuthzDomDataBroker implements BrokerService, DOMDataBroker { - - private DOMDataBroker domDataBroker; - private Broker.ProviderSession providerSession; - - private volatile AuthenticationService authService; - - final static AuthzDomDataBroker INSTANCE = new AuthzDomDataBroker(); - - public static AuthzDomDataBroker getInstance() { - return INSTANCE; - } - - public void setDomDataBroker(DOMDataBroker domDataBroker) { - this.domDataBroker = domDataBroker; - } - - public void setProviderSession(Broker.ProviderSession providerSession) { - this.providerSession = providerSession; - } - - public void setAuthService(AuthenticationService authService) { - this.authService = authService; - } - - public AuthenticationService getAuthService() { - return this.authService; - } - - @Override - public DOMDataReadOnlyTransaction newReadOnlyTransaction() { - // new Authz transaction + inject real DOM Transaction - DOMDataReadOnlyTransaction ro = domDataBroker.newReadOnlyTransaction(); - - // return domDataBroker.newReadOnlyTransaction(); //Return original - return new AuthzReadOnlyTransaction(ro); - } - - @Override - public Map, DOMDataBrokerExtension> getSupportedExtensions() { - return domDataBroker.getSupportedExtensions(); - } - - @Override - public DOMDataReadWriteTransaction newReadWriteTransaction() { - // return new Authz transaction + inject real DOM Transaction - DOMDataReadWriteTransaction rw = domDataBroker.newReadWriteTransaction(); - return new AuthzDataReadWriteTransaction(rw); - } - - @Override - public DOMDataWriteTransaction newWriteOnlyTransaction() { - DOMDataWriteTransaction wo = domDataBroker.newWriteOnlyTransaction(); - return new AuthzWriteOnlyTransaction(wo); - } - - @Override - public ListenerRegistration registerDataChangeListener( - LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier, - DOMDataChangeListener domDataChangeListener, DataChangeScope dataChangeScope) { - return domDataBroker.registerDataChangeListener(logicalDatastoreType, - yangInstanceIdentifier, domDataChangeListener, dataChangeScope); - } - - @Override - public DOMTransactionChain createTransactionChain( - TransactionChainListener transactionChainListener) { - return domDataBroker.createTransactionChain(transactionChainListener); - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzProviderContextImpl.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzProviderContextImpl.java deleted file mode 100644 index dbfea6ed..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzProviderContextImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; -import org.opendaylight.controller.sal.core.api.Broker; -import org.opendaylight.controller.sal.core.api.Broker.ProviderSession; -import org.opendaylight.controller.sal.core.api.BrokerService; -import org.opendaylight.controller.sal.core.spi.ForwardingProviderSession; - -/** - * Created by wdec on 28/08/2014. - */ -public class AuthzProviderContextImpl extends ForwardingProviderSession { - - private final Broker.ProviderSession realSession; - - public AuthzProviderContextImpl(Broker.ProviderSession providerSession, - AuthzBrokerImpl authzBroker) { - this.realSession = providerSession; - } - - @Override - protected ProviderSession delegate() { - // TODO Auto-generated method stub - return realSession; - } - - @Override - public T getService(Class tClass) { - T t; - // Check for class and return Authz broker only for DOMBroker - if (tClass == DOMDataBroker.class) { - t = (T) AuthzDomDataBroker.getInstance(); - } else { - t = realSession.getService(tClass); - } - // AuthzDomDataBroker.getInstance().setDomDataBroker((DOMDataBroker)t); - return t; - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzReadOnlyTransaction.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzReadOnlyTransaction.java deleted file mode 100644 index c46ffe7c..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzReadOnlyTransaction.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import com.google.common.base.Optional; -import com.google.common.util.concurrent.CheckedFuture; -import com.google.common.util.concurrent.Futures; - -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; -import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authz.ds.rev140722.ActionType; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; - -/** - * Created by wdec on 28/08/2014. - */ - -public class AuthzReadOnlyTransaction implements DOMDataReadOnlyTransaction { - - private final DOMDataReadOnlyTransaction ro; - - public AuthzReadOnlyTransaction(DOMDataReadOnlyTransaction ro) { - this.ro = ro; - } - - @Override - public void close() { - ro.close(); - } - - @Override - public CheckedFuture>, ReadFailedException> read( - LogicalDatastoreType logicalDatastoreType, YangInstanceIdentifier yangInstanceIdentifier) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Read)) { - return ro.read(logicalDatastoreType, yangInstanceIdentifier); - } - ReadFailedException e = new ReadFailedException("Authorization Failed"); - return Futures.immediateFailedCheckedFuture(e); - } - - @Override - public CheckedFuture exists( - LogicalDatastoreType logicalDatastoreType, YangInstanceIdentifier yangInstanceIdentifier) { - - if (AuthzServiceImpl.isAuthorized(ActionType.Exists)) { - return ro.exists(logicalDatastoreType, yangInstanceIdentifier); - } - ReadFailedException e = new ReadFailedException("Authorization Failed"); - return Futures.immediateFailedCheckedFuture(e); - } - - @Override - public Object getIdentifier() { - if (AuthzServiceImpl.isAuthorized(ActionType.GetIdentifier)) { - return ro.getIdentifier(); - } - return null; - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzServiceImpl.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzServiceImpl.java deleted file mode 100644 index fb344812..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzServiceImpl.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import java.util.List; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.controller.config.yang.config.aaa_authz.srv.Policies; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authz.ds.rev140722.ActionType; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authz.ds.rev140722.AuthorizationResponseType; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; - -/** - * @author lmukkama Date: 9/2/14 - */ -public class AuthzServiceImpl { - - private static List listPolicies; - - private static final String WILDCARD_TOKEN = "*"; - - public static boolean isAuthorized(LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier, ActionType actionType) { - - AuthorizationResponseType authorizationResponseType = AuthzServiceImpl.reqAuthorization( - actionType, logicalDatastoreType, yangInstanceIdentifier); - return authorizationResponseType.equals(AuthorizationResponseType.Authorized); - } - - public static boolean isAuthorized(ActionType actionType) { - AuthorizationResponseType authorizationResponseType = AuthzServiceImpl - .reqAuthorization(actionType); - return authorizationResponseType.equals(AuthorizationResponseType.Authorized); - } - - public static void setPolicies(List policies) { - - AuthzServiceImpl.listPolicies = policies; - } - - public static AuthorizationResponseType reqAuthorization(ActionType actionType) { - - AuthenticationService authenticationService = AuthzDomDataBroker.getInstance() - .getAuthService(); - if (authenticationService != null && AuthzServiceImpl.listPolicies != null - && AuthzServiceImpl.listPolicies.size() > 0) { - Authentication authentication = authenticationService.get(); - if (authentication != null && authentication.roles() != null - && authentication.roles().size() > 0) { - return checkAuthorization(actionType, authentication); - } - } - return AuthorizationResponseType.NotAuthorized; - } - - public static AuthorizationResponseType reqAuthorization(ActionType actionType, - LogicalDatastoreType logicalDatastoreType, YangInstanceIdentifier yangInstanceIdentifier) { - - AuthenticationService authenticationService = AuthzDomDataBroker.getInstance() - .getAuthService(); - - if (authenticationService != null && AuthzServiceImpl.listPolicies != null - && AuthzServiceImpl.listPolicies.size() > 0) { - // Authentication Service exists. Can do authorization checks - Authentication authentication = authenticationService.get(); - - if (authentication != null && authentication.roles() != null - && authentication.roles().size() > 0) { - // Authentication claim object exists with atleast one role - return checkAuthorization(actionType, authentication, logicalDatastoreType, - yangInstanceIdentifier); - } - } - - return AuthorizationResponseType.Authorized; - } - - private static AuthorizationResponseType checkAuthorization(ActionType actionType, - Authentication authentication, LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier) { - - for (Policies policy : AuthzServiceImpl.listPolicies) { - - // Action type is compared as string, since its type is string in - // the config yang. Comparison is case insensitive - if (authentication.roles().contains(policy.getRole().getValue()) - && (policy.getResource().getValue().equals(WILDCARD_TOKEN) || policy - .getResource().getValue().equals(yangInstanceIdentifier.toString())) - && (policy.getAction().toLowerCase() - .equals(ActionType.Any.name().toLowerCase()) || actionType.name() - .toLowerCase().equals(policy.getAction().toLowerCase()))) { - - return AuthorizationResponseType.Authorized; - } - - } - - // For helium release we unauthorize other requests. - return AuthorizationResponseType.NotAuthorized; - } - - private static AuthorizationResponseType checkAuthorization(ActionType actionType, - Authentication authentication) { - - for (Policies policy : AuthzServiceImpl.listPolicies) { - if (authentication.roles().contains(policy.getRole().getValue()) - && (policy.getAction().equalsIgnoreCase(ActionType.Any.name()) || policy - .getAction().equalsIgnoreCase(actionType.name()))) { - return AuthorizationResponseType.Authorized; - } - } - return AuthorizationResponseType.NotAuthorized; - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzWriteOnlyTransaction.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzWriteOnlyTransaction.java deleted file mode 100644 index 1123b928..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzWriteOnlyTransaction.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import com.google.common.util.concurrent.CheckedFuture; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; - -import org.opendaylight.controller.md.sal.common.api.TransactionStatus; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; -import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; -import org.opendaylight.yang.gen.v1.urn.aaa.yang.authz.ds.rev140722.ActionType; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; - -/** - * Created by wdec on 02/09/2014. - */ -public class AuthzWriteOnlyTransaction implements DOMDataWriteTransaction { - - private final DOMDataWriteTransaction domDataWriteTransaction; - - public AuthzWriteOnlyTransaction(DOMDataWriteTransaction wo) { - this.domDataWriteTransaction = wo; - } - - @Override - public void put(LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode normalizedNode) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Put)) { - domDataWriteTransaction.put(logicalDatastoreType, yangInstanceIdentifier, - normalizedNode); - } - } - - @Override - public void merge(LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode normalizedNode) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Merge)) { - domDataWriteTransaction.merge(logicalDatastoreType, yangInstanceIdentifier, - normalizedNode); - } - } - - @Override - public boolean cancel() { - if (AuthzServiceImpl.isAuthorized(ActionType.Cancel)) { - return domDataWriteTransaction.cancel(); - } - return false; - } - - @Override - public void delete(LogicalDatastoreType logicalDatastoreType, - YangInstanceIdentifier yangInstanceIdentifier) { - - if (AuthzServiceImpl.isAuthorized(logicalDatastoreType, yangInstanceIdentifier, - ActionType.Delete)) { - domDataWriteTransaction.delete(logicalDatastoreType, yangInstanceIdentifier); - } - } - - @Override - public CheckedFuture submit() { - if (AuthzServiceImpl.isAuthorized(ActionType.Submit)) { - return domDataWriteTransaction.submit(); - } - TransactionCommitFailedException e = new TransactionCommitFailedException( - "Unauthorized User"); - return Futures.immediateFailedCheckedFuture(e); - } - - @Deprecated - @Override - public ListenableFuture> commit() { - if (AuthzServiceImpl.isAuthorized(ActionType.Commit)) { - return domDataWriteTransaction.commit(); - } - TransactionCommitFailedException e = new TransactionCommitFailedException( - "Unauthorized User"); - return Futures.immediateFailedCheckedFuture(e); - } - - @Override - public Object getIdentifier() { - if (AuthzServiceImpl.isAuthorized(ActionType.GetIdentifier)) { - return domDataWriteTransaction.getIdentifier(); - } - return null; - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModule.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModule.java deleted file mode 100644 index a590b982..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModule.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.controller.config.yang.config.aaa_authz.srv; - -import org.opendaylight.aaa.api.AuthenticationService; -import org.opendaylight.aaa.authz.srv.AuthzBrokerImpl; -import org.opendaylight.aaa.authz.srv.AuthzServiceImpl; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class AuthzSrvModule extends - org.opendaylight.controller.config.yang.config.aaa_authz.srv.AbstractAuthzSrvModule { - private static final Logger LOG = LoggerFactory.getLogger(AuthzSrvModule.class); - private static boolean simple_config_switch; - private BundleContext bundleContext; - - public AuthzSrvModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, - org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { - super(identifier, dependencyResolver); - } - - public AuthzSrvModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, - org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, - org.opendaylight.controller.config.yang.config.aaa_authz.srv.AuthzSrvModule oldModule, - java.lang.AutoCloseable oldInstance) { - super(identifier, dependencyResolver, oldModule, oldInstance); - } - - @Override - public void customValidation() { - // checkNotNull(getDomBroker(), domBrokerJmxAttribute); - } - - @Override - public java.lang.AutoCloseable createInstance() { - - // Get new AuthZ Broker - final AuthzBrokerImpl authzBrokerImpl = new AuthzBrokerImpl(); - - // Provide real broker to the new Authz broker - authzBrokerImpl.setBroker(getDomBrokerDependency()); - - // Get AuthN service reference and register it with the authzBroker - ServiceReference authServiceReference = bundleContext - .getServiceReference(AuthenticationService.class); - AuthenticationService as = bundleContext.getService(authServiceReference); - authzBrokerImpl.setAuthenticationService(as); - - // Set the policies list to authz serviceimpl - AuthzServiceImpl.setPolicies(getPolicies()); - - // Register AuthZ broker with the real Broker as a provider; triggers - // "onSessionInitiated" in AuthzBrokerImpl - getDomBrokerDependency().registerProvider(authzBrokerImpl); - // TODO ActionType is of type string, not ENUM due to improper - // serialization of ENUMs by config/netconf subsystem. This needs to be - // fixed as soon as config/netconf fixes the problem. - getAction(); - - LOG.info("AuthZ Service Initialized from Config subsystem"); - return authzBrokerImpl; - - } - - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModuleFactory.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModuleFactory.java deleted file mode 100644 index 3ff67f54..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModuleFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -/* - * Generated file - * - * Generated from: yang module name: aaa-authz-service-impl yang module local name: aaa-authz-service - * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator - * Generated at: Thu Jul 24 11:19:40 CEST 2014 - * - * Do not modify this file unless it is present under src/main directory - */ -package org.opendaylight.controller.config.yang.config.aaa_authz.srv; - -import org.opendaylight.controller.config.api.DependencyResolver; -import org.opendaylight.controller.config.api.DynamicMBeanWithInstance; -import org.opendaylight.controller.config.spi.Module; -import org.osgi.framework.BundleContext; - -public class AuthzSrvModuleFactory extends - org.opendaylight.controller.config.yang.config.aaa_authz.srv.AbstractAuthzSrvModuleFactory { - - @Override - public org.opendaylight.controller.config.spi.Module createModule(String instanceName, - org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, - org.osgi.framework.BundleContext bundleContext) { - - final AuthzSrvModule module = (AuthzSrvModule) super.createModule(instanceName, - dependencyResolver, bundleContext); - - module.setBundleContext(bundleContext); - - return module; - - } - - @Override - public Module createModule(final String instanceName, - final DependencyResolver dependencyResolver, final DynamicMBeanWithInstance old, - final BundleContext bundleContext) throws Exception { - final AuthzSrvModule module = (AuthzSrvModule) super.createModule(instanceName, - dependencyResolver, old, bundleContext); - - module.setBundleContext(bundleContext); - - return module; - } -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/yang/aaa-authz-service-impl.yang b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/yang/aaa-authz-service-impl.yang deleted file mode 100644 index 954d0480..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/yang/aaa-authz-service-impl.yang +++ /dev/null @@ -1,115 +0,0 @@ -module aaa-authz-service-impl { - - yang-version 1; - namespace "urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv"; - prefix "aaa-authz-srv-impl"; - - import config { prefix config; revision-date 2013-04-05; } - import rpc-context { prefix rpcx; revision-date 2013-06-17; } - import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; } - import opendaylight-md-sal-dom {prefix dom;} - import authorization-schema { prefix authzs; revision-date 2014-07-22; } - import ietf-inet-types {prefix inet; revision-date 2010-09-24;} - - description - "This module contains the base YANG definitions for - AuthZ implementation."; - - revision "2014-07-01" { - description - "Initial revision."; - } - - - // This is the definition of the service implementation as a module identity. - identity aaa-authz-service { - base config:module-type; - // Specifies the prefix for generated java classes. - config:java-name-prefix AuthzSrv; - config:provided-service dom:dom-broker-osgi-registry; - } - - // Augments the 'configuration' choice node under modules/module. - - augment "/config:modules/config:module/config:configuration" { - case aaa-authz-service { - when "/config:modules/config:module/config:type = 'aaa-authz-service'"; - -//Defines reference to the intended broker under the AuthZ broker - - container dom-broker { - uses config:service-ref { - refine type { - mandatory true; - config:required-identity dom:dom-broker-osgi-registry; - } - } - } - - container data-broker { - uses config:service-ref { - refine type { - mandatory true; - config:required-identity mdsal:binding-data-broker; - - } - } - } - -//Simple Authz data leafs: - - leaf authz-role { - type string; - } - leaf service { - type authzs:service-type; - } - - // ENUMs cannot be used right now (config subsystem + netconf cannot properly serialize enums), using strings instead - // In the generated module use Enum.valueOf from that string. - // Expected values are following strnigs: create, read, update, delete, execute, subscribe, any; - leaf action { - type string; - description "String representation of enum authzs:action-type expecting following values create, read, update, delete, execute, subscribe, any"; - //type authzs:action-type; - - } - leaf resource { - type authzs:resource-type; - - } - leaf role { - type authzs:role-type; - } - - - - //TODO: Check why uses below doesn't make the outer list be part of the source name-space in yang code generator. - //uses authzs:authorization-grp; - list policies { - key "service"; - leaf service { - type authzs:service-type; - } - // Grouping uses ENUMs and enums are not correctly serialized in Config + Netconf - // Same as with action one level ip - leaf action { - type string; - description "String representation of enum authzs:action-type expecting following values create, read, update, delete, execute, subscribe, any"; - //type authzs:action-type; - } - leaf resource { - type authzs:resource-type; - - } - leaf role { - type authzs:role-type; - - } - } - - - } - } - -} diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/test/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImplTest.java b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/test/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImplTest.java deleted file mode 100644 index fb033341..00000000 --- a/odl-aaa-moon/aaa-authz/aaa-authz-service/src/test/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImplTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.authz.srv; - -import org.junit.Assert; -import org.junit.Before; -import org.mockito.Mockito; -import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; -import org.opendaylight.controller.sal.core.api.Broker; -import org.opendaylight.controller.sal.core.api.Provider; - -public class AuthzConsumerContextImplTest { - - private Broker.ConsumerSession realconsumercontext; - private Provider realprovidercontext; - private AuthzBrokerImpl authzBroker; - private Broker realbroker; - - @Before - public void beforeTest() { - realconsumercontext = Mockito.mock(Broker.ConsumerSession.class); - realprovidercontext = Mockito.mock(Provider.class); - realbroker = Mockito.mock(Broker.class); - realbroker.registerProvider(realprovidercontext); - authzBroker = Mockito.mock(AuthzBrokerImpl.class); - } - - @org.junit.Test - public void testGetService() throws Exception { - AuthzConsumerContextImpl authzConsumerContext = new AuthzConsumerContextImpl( - realconsumercontext, authzBroker); - - Assert.assertEquals("Expected Authz session context", - authzConsumerContext.getService(DOMDataBroker.class).getClass(), - AuthzDomDataBroker.class); - // Assert.assertEquals("Expected Authz session context", - // authzConsumerContext.getService(SchemaService.class).getClass(), - // SchemaService.class); - } -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authz/pom.xml b/odl-aaa-moon/aaa-authz/pom.xml deleted file mode 100644 index bdc1852f..00000000 --- a/odl-aaa-moon/aaa-authz/pom.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authz - ${project.artifactId} - pom - - - aaa-authz-model - aaa-authz-service - aaa-authz-config - aaa-authz-restconf-config - - diff --git a/odl-aaa-moon/aaa-credential-store-api/pom.xml b/odl-aaa-moon/aaa-credential-store-api/pom.xml deleted file mode 100644 index e7dfb81c..00000000 --- a/odl-aaa-moon/aaa-credential-store-api/pom.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - 4.0.0 - - org.opendaylight.mdsal - binding-parent - 0.8.1-Beryllium-SR1 - - - - org.opendaylight.aaa - aaa-credential-store-api - 0.3.1-Beryllium-SR1 - bundle - diff --git a/odl-aaa-moon/aaa-credential-store-api/src/main/yang/credential-model.yang b/odl-aaa-moon/aaa-credential-store-api/src/main/yang/credential-model.yang deleted file mode 100644 index 7d1f55a3..00000000 --- a/odl-aaa-moon/aaa-credential-store-api/src/main/yang/credential-model.yang +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -module credential-store { - namespace "urn:opendaylight:params:xml:ns:yang:aaa:credential-store"; - prefix "cs"; - - description "Defines and extensible model for storing various types of security credentials."; - - revision "2015-02-26" { description "Initial revision."; } - - identity credential-type { - description - "Credential base type. All credential types must be derived from this identity."; - } - - typedef credential-type-ref { - description "reference to an entry in the credential store based on id."; - type instance-identifier; - } - - container credential-store { - list credential { - key "id"; - - leaf id { - description "Unique identifier for this credential entry."; - type string; - } - - leaf type { - description "The type of credential represented in this entry."; - type identityref { - base credential-type; - } - } - - choice value { - description "Extension point. Contains the data specific to the credential type."; - } - } - } -} diff --git a/odl-aaa-moon/aaa-h2-store/.gitignore b/odl-aaa-moon/aaa-h2-store/.gitignore deleted file mode 100644 index 1dd33310..00000000 --- a/odl-aaa-moon/aaa-h2-store/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target/ -/target/ diff --git a/odl-aaa-moon/aaa-h2-store/pom.xml b/odl-aaa-moon/aaa-h2-store/pom.xml deleted file mode 100644 index 2b31525c..00000000 --- a/odl-aaa-moon/aaa-h2-store/pom.xml +++ /dev/null @@ -1,160 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-h2-store - bundle - - - - org.opendaylight.controller - config-api - ${config.version} - - - org.opendaylight.controller - sal-binding-config - - - org.opendaylight.controller - sal-binding-api - - - org.opendaylight.controller - sal-common-util - - - org.apache.commons - commons-lang3 - - - - org.opendaylight.aaa - aaa-authn-api - - - org.opendaylight.aaa - aaa-authn - - - org.slf4j - slf4j-api - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - org.mockito - mockito-all - test - - - - - com.h2database - h2 - - - - junit - junit - test - - - org.slf4j - slf4j-simple - test - - - - - - - org.apache.felix - maven-bundle-plugin - ${bundle.plugin.version} - true - - - com.google.*,org.opendaylight.aaa.api.*,org.apache.felix.*,org.slf4j.*,org.opendaylight.*,org.osgi.*,org.apache.commons.lang3 - org.h2.* - h2 - - - - - org.opendaylight.yangtools - yang-maven-plugin - ${yangtools.version} - - - config - - generate-sources - - - - - org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator - ${jmxGeneratorPath} - - urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang - - - - org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl - ${salGeneratorPath} - - - true - - - - - - org.opendaylight.mdsal - maven-sal-api-gen-plugin - ${yangtools.version} - jar - - - org.opendaylight.controller - yang-jmx-generator-plugin - ${config.version} - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - - attach-artifact - - package - - - - ${project.build.directory}/classes/initial/08-aaa-h2-store-config.xml - xml - config - - - - - - - - - diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/config/IdmLightConfig.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/config/IdmLightConfig.java deleted file mode 100644 index a35ca48f..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/config/IdmLightConfig.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.config; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Responsible for providing configuration properties for the IDMLight/H2 - * data store implementation. - * - * @author peter.mellquist@hp.com - * - */ -public class IdmLightConfig { - - private static final Logger LOG = LoggerFactory.getLogger(IdmLightConfig.class); - - /** - * The default timeout for db connections in seconds. - */ - private static final int DEFAULT_DB_TIMEOUT = 3; - - /** - * The default password for the database - */ - private static final String DEFAULT_PASSWORD = "bar"; - - /** - * The default username for the database - */ - private static final String DEFAULT_USERNAME = "foo"; - - /** - * The default driver for the databse is H2; a pure-java implementation - * of JDBC. - */ - private static final String DEFAULT_JDBC_DRIVER = "org.h2.Driver"; - - /** - * The default connection string includes the intention to use h2 as - * the JDBC driver, and the path for the file is located relative to - * KARAF_HOME. - */ - private static final String DEFAULT_CONNECTION_STRING = "jdbc:h2:./"; - - /** - * The default filename for the database file. - */ - private static final String DEFAULT_IDMLIGHT_DB_FILENAME = "idmlight.db"; - - /** - * The database filename - */ - private String dbName; - - /** - * the database connection string - */ - private String dbPath; - - /** - * The database driver (i.e., H2) - */ - private String dbDriver; - - /** - * The database password. This is not the same as AAA credentials! - */ - private String dbUser; - - /** - * The database username. This is not the same as AAA credentials! - */ - private String dbPwd; - - /** - * Timeout for database connections in seconds - */ - private int dbValidTimeOut; - - /** - * Creates an valid database configuration using default values. - */ - public IdmLightConfig() { - // TODO make this configurable - dbName = DEFAULT_IDMLIGHT_DB_FILENAME; - dbPath = DEFAULT_CONNECTION_STRING + dbName; - dbDriver = DEFAULT_JDBC_DRIVER; - dbUser = DEFAULT_USERNAME; - dbPwd = DEFAULT_PASSWORD; - dbValidTimeOut = DEFAULT_DB_TIMEOUT; - } - - /** - * Outputs some debugging information surrounding idmlight config - */ - public void log() { - LOG.info("DB Path : {}", dbPath); - LOG.info("DB Driver : {}", dbDriver); - LOG.info("DB Valid Time Out : {}", dbValidTimeOut); - } - - public String getDbName() { - return this.dbName; - } - - public String getDbPath() { - return this.dbPath; - } - - public String getDbDriver() { - return this.dbDriver; - } - - public String getDbUser() { - return this.dbUser; - } - - public String getDbPwd() { - return this.dbPwd; - } - - public int getDbValidTimeOut() { - return this.dbValidTimeOut; - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/AbstractStore.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/AbstractStore.java deleted file mode 100644 index ba00eb84..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/AbstractStore.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright © 2016 Red Hat, Inc. and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.h2.persistence; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Base class for H2 stores. - */ -abstract class AbstractStore { - /** - * Logger. - */ - private static final Logger LOG = LoggerFactory.getLogger(AbstractStore.class); - - /** - * The name of the table used to represent this store. - */ - private final String tableName; - - /** - * Database connection, only used for tests. - */ - Connection dbConnection = null; - - /** - * Table types we're interested in (when checking tables' existence). - */ - public static final String[] TABLE_TYPES = new String[] { "TABLE" }; - - /** - * Creates an instance. - * - * @param tableName The name of the table being managed. - */ - protected AbstractStore(String tableName) { - this.tableName = tableName; - } - - /** - * Returns a database connection. It is the caller's responsibility to close it. If the managed table does not - * exist, it will be created (using {@link #getTableCreationStatement()}). - * - * @return A database connection. - * - * @throws StoreException if an error occurs. - */ - protected Connection dbConnect() throws StoreException { - Connection conn = H2Store.getConnection(dbConnection); - try { - // Ensure table check/creation is atomic - synchronized (this) { - DatabaseMetaData dbm = conn.getMetaData(); - try (ResultSet rs = dbm.getTables(null, null, tableName, TABLE_TYPES)) { - if (rs.next()) { - LOG.debug("Table {} already exists", tableName); - } else { - LOG.info("Table {} does not exist, creating it", tableName); - try (Statement stmt = conn.createStatement()) { - stmt.executeUpdate(getTableCreationStatement()); - } - } - } - } - } catch (SQLException e) { - LOG.error("Error connecting to the H2 database", e); - throw new StoreException("Cannot connect to database server", e); - } - return conn; - } - - /** - * Empties the store. - * - * @throws StoreException if a connection error occurs. - */ - protected void dbClean() throws StoreException { - try (Connection c = dbConnect()) { - // The table name can't be a parameter in a prepared statement - String sql = "DELETE FROM " + tableName; - c.createStatement().execute(sql); - } catch (SQLException e) { - LOG.error("Error clearing table {}", tableName, e); - throw new StoreException("Error clearing table " + tableName, e); - } - } - - /** - * Returns the SQL code required to create the managed table. - * - * @return The SQL table creation statement. - */ - protected abstract String getTableCreationStatement(); - - /** - * Lists all the stored items. - * - * @return The stored item. - * - * @throws StoreException if an error occurs. - */ - protected List listAll() throws StoreException { - List result = new ArrayList<>(); - String query = "SELECT * FROM " + tableName; - try (Connection conn = dbConnect(); - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(query)) { - while (rs.next()) { - result.add(fromResultSet(rs)); - } - } catch (SQLException e) { - LOG.error("Error listing all items from {}", tableName, e); - throw new StoreException(e); - } - return result; - } - - /** - * Lists the stored items returned by the given statement. - * - * @param ps The statement (which must be ready for execution). It is the caller's reponsibility to close this. - * - * @return The stored items. - * - * @throws StoreException if an error occurs. - */ - protected List listFromStatement(PreparedStatement ps) throws StoreException { - List result = new ArrayList<>(); - try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - result.add(fromResultSet(rs)); - } - } catch (SQLException e) { - LOG.error("Error listing matching items from {}", tableName, e); - throw new StoreException(e); - } - return result; - } - - /** - * Extracts the first item returned by the given statement, if any. - * - * @param ps The statement (which must be ready for execution). It is the caller's reponsibility to close this. - * - * @return The first item, or {@code null} if none. - * - * @throws StoreException if an error occurs. - */ - protected T firstFromStatement(PreparedStatement ps) throws StoreException { - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - return fromResultSet(rs); - } else { - return null; - } - } catch (SQLException e) { - LOG.error("Error listing first matching item from {}", tableName, e); - throw new StoreException(e); - } - } - - /** - * Converts a single row in a result set to an instance of the managed type. - * - * @param rs The result set (which is ready for extraction; {@link ResultSet#next()} must not be called). - * - * @return The corresponding instance. - * - * @throws SQLException if an error occurs. - */ - protected abstract T fromResultSet(ResultSet rs) throws SQLException; -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/DomainStore.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/DomainStore.java deleted file mode 100644 index aa8f4b30..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/DomainStore.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2014, 2016 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import com.google.common.base.Preconditions; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.apache.commons.lang3.StringEscapeUtils; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Domains; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author peter.mellquist@hp.com - * - */ -public class DomainStore extends AbstractStore { - private static final Logger LOG = LoggerFactory.getLogger(DomainStore.class); - - protected final static String SQL_ID = "domainid"; - protected final static String SQL_NAME = "name"; - protected final static String SQL_DESCR = "description"; - protected final static String SQL_ENABLED = "enabled"; - private static final String TABLE_NAME = "DOMAINS"; - - protected DomainStore() { - super(TABLE_NAME); - } - - @Override - protected String getTableCreationStatement() { - return "CREATE TABLE DOMAINS " - + "(domainid VARCHAR(128) PRIMARY KEY," - + "name VARCHAR(128) UNIQUE NOT NULL, " - + "description VARCHAR(128) , " - + "enabled INTEGER NOT NULL)"; - } - - @Override - protected Domain fromResultSet(ResultSet rs) throws SQLException { - Domain domain = new Domain(); - domain.setDomainid(rs.getString(SQL_ID)); - domain.setName(rs.getString(SQL_NAME)); - domain.setDescription(rs.getString(SQL_DESCR)); - domain.setEnabled(rs.getInt(SQL_ENABLED) == 1); - return domain; - } - - protected Domains getDomains() throws StoreException { - Domains domains = new Domains(); - domains.setDomains(listAll()); - return domains; - } - - protected Domains getDomains(String domainName) throws StoreException { - LOG.debug("getDomains for: {}", domainName); - Domains domains = new Domains(); - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM DOMAINS WHERE name = ?")) { - pstmt.setString(1, domainName); - LOG.debug("query string: {}", pstmt.toString()); - domains.setDomains(listFromStatement(pstmt)); - } catch (SQLException e) { - LOG.error("Error listing domains matching {}", domainName, e); - throw new StoreException("Error listing domains", e); - } - return domains; - } - - protected Domain getDomain(String id) throws StoreException { - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM DOMAINS WHERE domainid = ? ")) { - pstmt.setString(1, id); - LOG.debug("query string: {}", pstmt.toString()); - return firstFromStatement(pstmt); - } catch (SQLException e) { - LOG.error("Error retrieving domain {}", id, e); - throw new StoreException("Error loading domain", e); - } - } - - protected Domain createDomain(Domain domain) throws StoreException { - Preconditions.checkNotNull(domain); - Preconditions.checkNotNull(domain.getName()); - Preconditions.checkNotNull(domain.isEnabled()); - String query = "insert into DOMAINS (domainid,name,description,enabled) values(?, ?, ?, ?)"; - try (Connection conn = dbConnect(); - PreparedStatement statement = conn.prepareStatement(query)) { - statement.setString(1, domain.getName()); - statement.setString(2, domain.getName()); - statement.setString(3, domain.getDescription()); - statement.setInt(4, domain.isEnabled() ? 1 : 0); - int affectedRows = statement.executeUpdate(); - if (affectedRows == 0) { - throw new StoreException("Creating domain failed, no rows affected."); - } - domain.setDomainid(domain.getName()); - return domain; - } catch (SQLException e) { - LOG.error("Error creating domain {}", domain.getName(), e); - throw new StoreException("Error creating domain", e); - } - } - - protected Domain putDomain(Domain domain) throws StoreException { - Domain savedDomain = this.getDomain(domain.getDomainid()); - if (savedDomain == null) { - return null; - } - - if (domain.getDescription() != null) { - savedDomain.setDescription(domain.getDescription()); - } - if (domain.getName() != null) { - savedDomain.setName(domain.getName()); - } - if (domain.isEnabled() != null) { - savedDomain.setEnabled(domain.isEnabled()); - } - - String query = "UPDATE DOMAINS SET description = ?, enabled = ? WHERE domainid = ?"; - try (Connection conn = dbConnect(); - PreparedStatement statement = conn.prepareStatement(query)) { - statement.setString(1, savedDomain.getDescription()); - statement.setInt(2, savedDomain.isEnabled() ? 1 : 0); - statement.setString(3, savedDomain.getDomainid()); - statement.executeUpdate(); - } catch (SQLException e) { - LOG.error("Error updating domain {}", domain.getDomainid(), e); - throw new StoreException("Error updating domain", e); - } - - return savedDomain; - } - - protected Domain deleteDomain(String domainid) throws StoreException { - domainid = StringEscapeUtils.escapeHtml4(domainid); - Domain deletedDomain = this.getDomain(domainid); - if (deletedDomain == null) { - return null; - } - String query = String.format("DELETE FROM DOMAINS WHERE domainid = '%s'", domainid); - try (Connection conn = dbConnect(); - Statement statement = conn.createStatement()) { - int deleteCount = statement.executeUpdate(query); - LOG.debug("deleted {} records", deleteCount); - return deletedDomain; - } catch (SQLException e) { - LOG.error("Error deleting domain {}", domainid, e); - throw new StoreException("Error deleting domain", e); - } - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/GrantStore.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/GrantStore.java deleted file mode 100644 index ee86e0ba..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/GrantStore.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2014, 2016 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.apache.commons.lang3.StringEscapeUtils; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Grants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author peter.mellquist@hp.com - * - */ -public class GrantStore extends AbstractStore { - private static final Logger LOG = LoggerFactory.getLogger(GrantStore.class); - - protected final static String SQL_ID = "grantid"; - protected final static String SQL_TENANTID = "domainid"; - protected final static String SQL_USERID = "userid"; - protected final static String SQL_ROLEID = "roleid"; - private static final String TABLE_NAME = "GRANTS"; - - protected GrantStore() { - super(TABLE_NAME); - } - - @Override - protected String getTableCreationStatement() { - return "CREATE TABLE GRANTS " - + "(grantid VARCHAR(128) PRIMARY KEY," - + "domainid VARCHAR(128) NOT NULL, " - + "userid VARCHAR(128) NOT NULL, " - + "roleid VARCHAR(128) NOT NULL)"; - } - - protected Grant fromResultSet(ResultSet rs) throws SQLException { - Grant grant = new Grant(); - try { - grant.setGrantid(rs.getString(SQL_ID)); - grant.setDomainid(rs.getString(SQL_TENANTID)); - grant.setUserid(rs.getString(SQL_USERID)); - grant.setRoleid(rs.getString(SQL_ROLEID)); - } catch (SQLException sqle) { - LOG.error("SQL Exception: ", sqle); - throw sqle; - } - return grant; - } - - protected Grants getGrants(String did, String uid) throws StoreException { - Grants grants = new Grants(); - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn - .prepareStatement("SELECT * FROM grants WHERE domainid = ? AND userid = ?")) { - pstmt.setString(1, did); - pstmt.setString(2, uid); - LOG.debug("query string: {}", pstmt.toString()); - grants.setGrants(listFromStatement(pstmt)); - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - return grants; - } - - protected Grants getGrants(String userid) throws StoreException { - Grants grants = new Grants(); - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM GRANTS WHERE userid = ? ")) { - pstmt.setString(1, userid); - LOG.debug("query string: {}", pstmt.toString()); - grants.setGrants(listFromStatement(pstmt)); - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - return grants; - } - - protected Grant getGrant(String id) throws StoreException { - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM GRANTS WHERE grantid = ? ")) { - pstmt.setString(1, id); - LOG.debug("query string: ", pstmt.toString()); - return firstFromStatement(pstmt); - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } - - protected Grant getGrant(String did, String uid, String rid) throws StoreException { - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn - .prepareStatement("SELECT * FROM GRANTS WHERE domainid = ? AND userid = ? AND roleid = ? ")) { - pstmt.setString(1, did); - pstmt.setString(2, uid); - pstmt.setString(3, rid); - LOG.debug("query string: {}", pstmt.toString()); - return firstFromStatement(pstmt); - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } - - protected Grant createGrant(Grant grant) throws StoreException { - String query = "insert into grants (grantid,domainid,userid,roleid) values(?,?,?,?)"; - try (Connection conn = dbConnect(); - PreparedStatement statement = conn.prepareStatement(query)) { - statement.setString( - 1, - IDMStoreUtil.createGrantid(grant.getUserid(), grant.getDomainid(), - grant.getRoleid())); - statement.setString(2, grant.getDomainid()); - statement.setString(3, grant.getUserid()); - statement.setString(4, grant.getRoleid()); - int affectedRows = statement.executeUpdate(); - if (affectedRows == 0) { - throw new StoreException("Creating grant failed, no rows affected."); - } - grant.setGrantid(IDMStoreUtil.createGrantid(grant.getUserid(), grant.getDomainid(), - grant.getRoleid())); - return grant; - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } - - protected Grant deleteGrant(String grantid) throws StoreException { - grantid = StringEscapeUtils.escapeHtml4(grantid); - Grant savedGrant = this.getGrant(grantid); - if (savedGrant == null) { - return null; - } - - String query = String.format("DELETE FROM GRANTS WHERE grantid = '%s'", grantid); - try (Connection conn = dbConnect(); - Statement statement = conn.createStatement()) { - int deleteCount = statement.executeUpdate(query); - LOG.debug("deleted {} records", deleteCount); - return savedGrant; - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/H2Store.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/H2Store.java deleted file mode 100644 index da40a17b..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/H2Store.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import java.sql.Connection; -import java.sql.DriverManager; - -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Domains; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Grants; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.Roles; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.aaa.api.model.Users; -import org.opendaylight.aaa.h2.config.IdmLightConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class H2Store implements IIDMStore { - - private static final Logger LOG = LoggerFactory.getLogger(H2Store.class); - - private static IdmLightConfig config = new IdmLightConfig(); - private DomainStore domainStore = new DomainStore(); - private UserStore userStore = new UserStore(); - private RoleStore roleStore = new RoleStore(); - private GrantStore grantStore = new GrantStore(); - - public H2Store() { - } - - public static Connection getConnection(Connection existingConnection) throws StoreException { - Connection connection = existingConnection; - try { - if (existingConnection == null || existingConnection.isClosed()) { - new org.h2.Driver(); - connection = DriverManager.getConnection(config.getDbPath(), config.getDbUser(), - config.getDbPwd()); - } - } catch (Exception e) { - throw new StoreException("Cannot connect to database server" + e); - } - - return connection; - } - - public static IdmLightConfig getConfig() { - return config; - } - - @Override - public Domain writeDomain(Domain domain) throws IDMStoreException { - try { - return domainStore.createDomain(domain); - } catch (StoreException e) { - LOG.error("StoreException encountered while writing domain", e); - throw new IDMStoreException(e); - } - } - - @Override - public Domain readDomain(String domainid) throws IDMStoreException { - try { - return domainStore.getDomain(domainid); - } catch (StoreException e) { - LOG.error("StoreException encountered while reading domain", e); - throw new IDMStoreException(e); - } - } - - @Override - public Domain deleteDomain(String domainid) throws IDMStoreException { - try { - return domainStore.deleteDomain(domainid); - } catch (StoreException e) { - LOG.error("StoreException encountered while deleting domain", e); - throw new IDMStoreException(e); - } - } - - @Override - public Domain updateDomain(Domain domain) throws IDMStoreException { - try { - return domainStore.putDomain(domain); - } catch (StoreException e) { - LOG.error("StoreException encountered while updating domain", e); - throw new IDMStoreException(e); - } - } - - @Override - public Domains getDomains() throws IDMStoreException { - try { - return domainStore.getDomains(); - } catch (StoreException e) { - LOG.error("StoreException encountered while reading domains", e); - throw new IDMStoreException(e); - } - } - - @Override - public Role writeRole(Role role) throws IDMStoreException { - try { - return roleStore.createRole(role); - } catch (StoreException e) { - LOG.error("StoreException encountered while writing role", e); - throw new IDMStoreException(e); - } - } - - @Override - public Role readRole(String roleid) throws IDMStoreException { - try { - return roleStore.getRole(roleid); - } catch (StoreException e) { - LOG.error("StoreException encountered while reading role", e); - throw new IDMStoreException(e); - } - } - - @Override - public Role deleteRole(String roleid) throws IDMStoreException { - try { - return roleStore.deleteRole(roleid); - } catch (StoreException e) { - LOG.error("StoreException encountered while deleting role", e); - throw new IDMStoreException(e); - } - } - - @Override - public Role updateRole(Role role) throws IDMStoreException { - try { - return roleStore.putRole(role); - } catch (StoreException e) { - LOG.error("StoreException encountered while updating role", e); - throw new IDMStoreException(e); - } - } - - @Override - public Roles getRoles() throws IDMStoreException { - try { - return roleStore.getRoles(); - } catch (StoreException e) { - LOG.error("StoreException encountered while getting roles", e); - throw new IDMStoreException(e); - } - } - - @Override - public User writeUser(User user) throws IDMStoreException { - try { - return userStore.createUser(user); - } catch (StoreException e) { - LOG.error("StoreException encountered while writing user", e); - throw new IDMStoreException(e); - } - } - - @Override - public User readUser(String userid) throws IDMStoreException { - try { - return userStore.getUser(userid); - } catch (StoreException e) { - LOG.error("StoreException encountered while reading user", e); - throw new IDMStoreException(e); - } - } - - @Override - public User deleteUser(String userid) throws IDMStoreException { - try { - return userStore.deleteUser(userid); - } catch (StoreException e) { - LOG.error("StoreException encountered while deleting user", e); - throw new IDMStoreException(e); - } - } - - @Override - public User updateUser(User user) throws IDMStoreException { - try { - return userStore.putUser(user); - } catch (StoreException e) { - LOG.error("StoreException encountered while updating user", e); - throw new IDMStoreException(e); - } - } - - @Override - public Users getUsers(String username, String domain) throws IDMStoreException { - try { - return userStore.getUsers(username, domain); - } catch (StoreException e) { - LOG.error("StoreException encountered while reading users", e); - throw new IDMStoreException(e); - } - } - - @Override - public Users getUsers() throws IDMStoreException { - try { - return userStore.getUsers(); - } catch (StoreException e) { - LOG.error("StoreException encountered while reading users", e); - throw new IDMStoreException(e); - } - } - - @Override - public Grant writeGrant(Grant grant) throws IDMStoreException { - try { - return grantStore.createGrant(grant); - } catch (StoreException e) { - LOG.error("StoreException encountered while writing grant", e); - throw new IDMStoreException(e); - } - } - - @Override - public Grant readGrant(String grantid) throws IDMStoreException { - try { - return grantStore.getGrant(grantid); - } catch (StoreException e) { - LOG.error("StoreException encountered while reading grant", e); - throw new IDMStoreException(e); - } - } - - @Override - public Grant deleteGrant(String grantid) throws IDMStoreException { - try { - return grantStore.deleteGrant(grantid); - } catch (StoreException e) { - LOG.error("StoreException encountered while deleting grant", e); - throw new IDMStoreException(e); - } - } - - @Override - public Grants getGrants(String domainid, String userid) throws IDMStoreException { - try { - return grantStore.getGrants(domainid, userid); - } catch (StoreException e) { - LOG.error("StoreException encountered while getting grants", e); - throw new IDMStoreException(e); - } - } - - @Override - public Grants getGrants(String userid) throws IDMStoreException { - try { - return grantStore.getGrants(userid); - } catch (StoreException e) { - LOG.error("StoreException encountered while getting grants", e); - throw new IDMStoreException(e); - } - } - - @Override - public Grant readGrant(String domainid, String userid, String roleid) throws IDMStoreException { - return readGrant(IDMStoreUtil.createGrantid(userid, domainid, roleid)); - } - - public static Domain createDomain(String domainName, boolean enable) throws StoreException { - DomainStore ds = new DomainStore(); - Domain d = new Domain(); - d.setName(domainName); - d.setEnabled(enable); - return ds.createDomain(d); - } - - public static User createUser(String name, String password, String domain, String description, - String email, boolean enabled, String SALT) throws StoreException { - UserStore us = new UserStore(); - User u = new User(); - u.setName(name); - u.setDomainid(domain); - u.setDescription(description); - u.setEmail(email); - u.setEnabled(enabled); - u.setPassword(password); - u.setSalt(SALT); - return us.createUser(u); - } - - public static Role createRole(String name, String domain, String description) - throws StoreException { - RoleStore rs = new RoleStore(); - Role r = new Role(); - r.setDescription(description); - r.setName(name); - r.setDomainid(domain); - return rs.createRole(r); - } - - public static Grant createGrant(String domain, String user, String role) throws StoreException { - GrantStore gs = new GrantStore(); - Grant g = new Grant(); - g.setDomainid(domain); - g.setRoleid(role); - g.setUserid(user); - return gs.createGrant(g); - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/RoleStore.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/RoleStore.java deleted file mode 100644 index e7defa4a..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/RoleStore.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2014, 2016 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import com.google.common.base.Preconditions; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.apache.commons.lang3.StringEscapeUtils; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.Roles; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author peter.mellquist@hp.com - * - */ -public class RoleStore extends AbstractStore { - private static final Logger LOG = LoggerFactory.getLogger(RoleStore.class); - - protected final static String SQL_ID = "roleid"; - protected final static String SQL_DOMAIN_ID = "domainid"; - protected final static String SQL_NAME = "name"; - protected final static String SQL_DESCR = "description"; - private static final String TABLE_NAME = "ROLES"; - - protected RoleStore() { - super(TABLE_NAME); - } - - @Override - protected String getTableCreationStatement() { - return "CREATE TABLE ROLES " - + "(roleid VARCHAR(128) PRIMARY KEY," - + "name VARCHAR(128) NOT NULL, " - + "domainid VARCHAR(128) NOT NULL, " - + "description VARCHAR(128) NOT NULL)"; - } - - protected Role fromResultSet(ResultSet rs) throws SQLException { - Role role = new Role(); - try { - role.setRoleid(rs.getString(SQL_ID)); - role.setDomainid(rs.getString(SQL_DOMAIN_ID)); - role.setName(rs.getString(SQL_NAME)); - role.setDescription(rs.getString(SQL_DESCR)); - } catch (SQLException sqle) { - LOG.error("SQL Exception: ", sqle); - throw sqle; - } - return role; - } - - protected Roles getRoles() throws StoreException { - Roles roles = new Roles(); - roles.setRoles(listAll()); - return roles; - } - - protected Role getRole(String id) throws StoreException { - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn - .prepareStatement("SELECT * FROM ROLES WHERE roleid = ? ")) { - pstmt.setString(1, id); - LOG.debug("query string: {}", pstmt.toString()); - return firstFromStatement(pstmt); - } catch (SQLException s) { - throw new StoreException("SQL Exception: " + s); - } - } - - protected Role createRole(Role role) throws StoreException { - Preconditions.checkNotNull(role); - Preconditions.checkNotNull(role.getName()); - Preconditions.checkNotNull(role.getDomainid()); - String query = "insert into roles (roleid,domainid,name,description) values(?,?,?,?)"; - try (Connection conn = dbConnect(); - PreparedStatement statement = conn.prepareStatement(query)) { - role.setRoleid(IDMStoreUtil.createRoleid(role.getName(), role.getDomainid())); - statement.setString(1, role.getRoleid()); - statement.setString(2, role.getDomainid()); - statement.setString(3, role.getName()); - statement.setString(4, role.getDescription()); - int affectedRows = statement.executeUpdate(); - if (affectedRows == 0) { - throw new StoreException("Creating role failed, no rows affected."); - } - return role; - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } - - protected Role putRole(Role role) throws StoreException { - - Role savedRole = this.getRole(role.getRoleid()); - if (savedRole == null) { - return null; - } - - if (role.getDescription() != null) { - savedRole.setDescription(role.getDescription()); - } - if (role.getName() != null) { - savedRole.setName(role.getName()); - } - - String query = "UPDATE roles SET description = ? WHERE roleid = ?"; - try (Connection conn = dbConnect(); - PreparedStatement statement = conn.prepareStatement(query)) { - statement.setString(1, savedRole.getDescription()); - statement.setString(2, savedRole.getRoleid()); - statement.executeUpdate(); - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - - return savedRole; - } - - protected Role deleteRole(String roleid) throws StoreException { - roleid = StringEscapeUtils.escapeHtml4(roleid); - Role savedRole = this.getRole(roleid); - if (savedRole == null) { - return null; - } - - String query = String.format("DELETE FROM ROLES WHERE roleid = '%s'", roleid); - try (Connection conn = dbConnect(); - Statement statement = conn.createStatement()) { - int deleteCount = statement.executeUpdate(query); - LOG.debug("deleted {} records", deleteCount); - return savedRole; - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/StoreException.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/StoreException.java deleted file mode 100644 index 7d2f2b9a..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/StoreException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2014, 2016 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -/** - * Exception indicating an error in an H2 data store. - * - * @author peter.mellquist@hp.com - */ - -public class StoreException extends Exception { - public StoreException(String message) { - super(message); - } - - public StoreException(String message, Throwable cause) { - super(message, cause); - } - - public StoreException(Throwable cause) { - super(cause); - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/UserStore.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/UserStore.java deleted file mode 100644 index 96b8013f..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/UserStore.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2014, 2016 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import com.google.common.base.Preconditions; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.apache.commons.lang3.StringEscapeUtils; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.aaa.api.SHA256Calculator; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.aaa.api.model.Users; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author peter.mellquist@hp.com - * - */ -public class UserStore extends AbstractStore { - private static final Logger LOG = LoggerFactory.getLogger(UserStore.class); - - protected final static String SQL_ID = "userid"; - protected final static String SQL_DOMAIN_ID = "domainid"; - protected final static String SQL_NAME = "name"; - protected final static String SQL_EMAIL = "email"; - protected final static String SQL_PASSWORD = "password"; - protected final static String SQL_DESCR = "description"; - protected final static String SQL_ENABLED = "enabled"; - protected final static String SQL_SALT = "salt"; - private static final String TABLE_NAME = "USERS"; - - protected UserStore() { - super(TABLE_NAME); - } - - @Override - protected String getTableCreationStatement() { - return "CREATE TABLE users " - + "(userid VARCHAR(128) PRIMARY KEY," - + "name VARCHAR(128) NOT NULL, " - + "domainid VARCHAR(128) NOT NULL, " - + "email VARCHAR(128) NOT NULL, " - + "password VARCHAR(128) NOT NULL, " - + "description VARCHAR(128) NOT NULL, " - + "salt VARCHAR(15) NOT NULL, " - + "enabled INTEGER NOT NULL)"; - } - - @Override - protected User fromResultSet(ResultSet rs) throws SQLException { - User user = new User(); - try { - user.setUserid(rs.getString(SQL_ID)); - user.setDomainid(rs.getString(SQL_DOMAIN_ID)); - user.setName(rs.getString(SQL_NAME)); - user.setEmail(rs.getString(SQL_EMAIL)); - user.setPassword(rs.getString(SQL_PASSWORD)); - user.setDescription(rs.getString(SQL_DESCR)); - user.setEnabled(rs.getInt(SQL_ENABLED) == 1); - user.setSalt(rs.getString(SQL_SALT)); - } catch (SQLException sqle) { - LOG.error("SQL Exception: ", sqle); - throw sqle; - } - return user; - } - - protected Users getUsers() throws StoreException { - Users users = new Users(); - users.setUsers(listAll()); - return users; - } - - protected Users getUsers(String username, String domain) throws StoreException { - LOG.debug("getUsers for: {} in domain {}", username, domain); - - Users users = new Users(); - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM USERS WHERE userid = ? ")) { - pstmt.setString(1, IDMStoreUtil.createUserid(username, domain)); - LOG.debug("query string: {}", pstmt.toString()); - users.setUsers(listFromStatement(pstmt)); - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - return users; - } - - protected User getUser(String id) throws StoreException { - try (Connection conn = dbConnect(); - PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM USERS WHERE userid = ? ")) { - pstmt.setString(1, id); - LOG.debug("query string: {}", pstmt.toString()); - return firstFromStatement(pstmt); - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } - - protected User createUser(User user) throws StoreException { - Preconditions.checkNotNull(user); - Preconditions.checkNotNull(user.getName()); - Preconditions.checkNotNull(user.getDomainid()); - - user.setSalt(SHA256Calculator.generateSALT()); - String query = "insert into users (userid,domainid,name,email,password,description,enabled,salt) values(?,?,?,?,?,?,?,?)"; - try (Connection conn = dbConnect(); - PreparedStatement statement = conn.prepareStatement(query)) { - user.setUserid(IDMStoreUtil.createUserid(user.getName(), user.getDomainid())); - statement.setString(1, user.getUserid()); - statement.setString(2, user.getDomainid()); - statement.setString(3, user.getName()); - statement.setString(4, user.getEmail()); - statement.setString(5, SHA256Calculator.getSHA256(user.getPassword(), user.getSalt())); - statement.setString(6, user.getDescription()); - statement.setInt(7, user.isEnabled() ? 1 : 0); - statement.setString(8, user.getSalt()); - int affectedRows = statement.executeUpdate(); - if (affectedRows == 0) { - throw new StoreException("Creating user failed, no rows affected."); - } - return user; - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } - - protected User putUser(User user) throws StoreException { - - User savedUser = this.getUser(user.getUserid()); - if (savedUser == null) { - return null; - } - - if (user.getDescription() != null) { - savedUser.setDescription(user.getDescription()); - } - if (user.getName() != null) { - savedUser.setName(user.getName()); - } - if (user.isEnabled() != null) { - savedUser.setEnabled(user.isEnabled()); - } - if (user.getEmail() != null) { - savedUser.setEmail(user.getEmail()); - } - if (user.getPassword() != null) { - // If a new salt is provided, use it. Otherwise, derive salt from existing. - String salt = user.getSalt(); - if (salt == null) { - salt = savedUser.getSalt(); - } - savedUser.setPassword(SHA256Calculator.getSHA256(user.getPassword(), salt)); - } - - String query = "UPDATE users SET email = ?, password = ?, description = ?, enabled = ? WHERE userid = ?"; - try (Connection conn = dbConnect(); - PreparedStatement statement = conn.prepareStatement(query)) { - statement.setString(1, savedUser.getEmail()); - statement.setString(2, savedUser.getPassword()); - statement.setString(3, savedUser.getDescription()); - statement.setInt(4, savedUser.isEnabled() ? 1 : 0); - statement.setString(5, savedUser.getUserid()); - statement.executeUpdate(); - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - - return savedUser; - } - - protected User deleteUser(String userid) throws StoreException { - userid = StringEscapeUtils.escapeHtml4(userid); - User savedUser = this.getUser(userid); - if (savedUser == null) { - return null; - } - - String query = String.format("DELETE FROM USERS WHERE userid = '%s'", userid); - try (Connection conn = dbConnect(); - Statement statement = conn.createStatement()) { - int deleteCount = statement.executeUpdate(query); - LOG.debug("deleted {} records", deleteCount); - return savedUser; - } catch (SQLException s) { - throw new StoreException("SQL Exception : " + s); - } - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModule.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModule.java deleted file mode 100644 index fe7dd2a6..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModule.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.opendaylight.yang.gen.v1.config.aaa.authn.h2.store.rev151128; - -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.h2.persistence.H2Store; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class AAAH2StoreModule extends org.opendaylight.yang.gen.v1.config.aaa.authn.h2.store.rev151128.AbstractAAAH2StoreModule { - - private BundleContext bundleContext; - private static final Logger LOG = LoggerFactory.getLogger(AAAH2StoreModule.class); - - public AAAH2StoreModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { - super(identifier, dependencyResolver); - } - - public AAAH2StoreModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.yang.gen.v1.config.aaa.authn.h2.store.rev151128.AAAH2StoreModule oldModule, java.lang.AutoCloseable oldInstance) { - super(identifier, dependencyResolver, oldModule, oldInstance); - } - - @Override - public java.lang.AutoCloseable createInstance() { - final H2Store h2Store = new H2Store(); - final ServiceRegistration serviceRegistration = bundleContext.registerService(IIDMStore.class.getName(), h2Store, null); - LOG.info("AAA H2 Store Initialized"); - return new AutoCloseable() { - @Override - public void close() throws Exception { - serviceRegistration.unregister(); - } - }; - } - - /** - * @param bundleContext - */ - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - /** - * @return the bundleContext - */ - public BundleContext getBundleContext() { - return bundleContext; - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModuleFactory.java b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModuleFactory.java deleted file mode 100644 index dc9e7f99..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModuleFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* -* Generated file -* -* Generated from: yang module name: aaa-h2-store yang module local name: aaa-h2-store -* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator -* Generated at: Sat Nov 28 11:00:15 PST 2015 -* -* Do not modify this file unless it is present under src/main directory -*/ -package org.opendaylight.yang.gen.v1.config.aaa.authn.h2.store.rev151128; - -import org.opendaylight.controller.config.api.DependencyResolver; -import org.osgi.framework.BundleContext; - -public class AAAH2StoreModuleFactory extends org.opendaylight.yang.gen.v1.config.aaa.authn.h2.store.rev151128.AbstractAAAH2StoreModuleFactory { - @Override - public AAAH2StoreModule instantiateModule(String instanceName, DependencyResolver dependencyResolver, AAAH2StoreModule oldModule, AutoCloseable oldInstance, BundleContext bundleContext) { - AAAH2StoreModule module = super.instantiateModule(instanceName, dependencyResolver, oldModule, oldInstance, bundleContext); - module.setBundleContext(bundleContext); - return module; - } - - @Override - public AAAH2StoreModule instantiateModule(String instanceName, DependencyResolver dependencyResolver, BundleContext bundleContext) { - AAAH2StoreModule module = super.instantiateModule(instanceName, dependencyResolver, bundleContext); - module.setBundleContext(bundleContext); - return module; - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/main/resources/initial/08-aaa-h2-store-config.xml b/odl-aaa-moon/aaa-h2-store/src/main/resources/initial/08-aaa-h2-store-config.xml deleted file mode 100644 index cfe60812..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/resources/initial/08-aaa-h2-store-config.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - authn:aaa-h2-store - aaa-h2-store - - - - - - config:aaa:authn:h2:store?module=aaa-h2-store&revision=2015-11-28 - - - - diff --git a/odl-aaa-moon/aaa-h2-store/src/main/yang/aaa-h2-store.yang b/odl-aaa-moon/aaa-h2-store/src/main/yang/aaa-h2-store.yang deleted file mode 100644 index af2d9bdc..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/main/yang/aaa-h2-store.yang +++ /dev/null @@ -1,28 +0,0 @@ -module aaa-h2-store { - yang-version 1; - namespace "config:aaa:authn:h2:store"; - prefix "aaa-h2-store"; - organization "OpenDayLight"; - - import config { prefix config; revision-date 2013-04-05; } - import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; } - - contact "saichler@gmail.com"; - - revision 2015-11-28 { - description - "Initial revision."; - } - - identity aaa-h2-store { - base config:module-type; - config:java-name-prefix AAAH2Store; - } - - augment "/config:modules/config:module/config:configuration" { - case aaa-h2-store { - when "/config:modules/config:module/config:type = 'aaa-h2-store'"; - } - } - -} diff --git a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/DomainStoreTest.java b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/DomainStoreTest.java deleted file mode 100644 index f11a99eb..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/DomainStoreTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.opendaylight.aaa.api.model.Domains; -import org.opendaylight.aaa.h2.persistence.DomainStore; - -public class DomainStoreTest { - - Connection connectionMock = mock(Connection.class); - private final DomainStore domainStoreUnderTest = new DomainStore(); - - @Before - public void setup() { - domainStoreUnderTest.dbConnection = connectionMock; - } - - @After - public void teardown() { - // dts.destroy(); - } - - @Test - public void getDomainsTest() throws SQLException, Exception { - // Setup Mock Behavior - String[] tableTypes = { "TABLE" }; - Mockito.when(connectionMock.isClosed()).thenReturn(false); - DatabaseMetaData dbmMock = mock(DatabaseMetaData.class); - Mockito.when(connectionMock.getMetaData()).thenReturn(dbmMock); - ResultSet rsUserMock = mock(ResultSet.class); - Mockito.when(dbmMock.getTables(null, null, "DOMAINS", tableTypes)).thenReturn(rsUserMock); - Mockito.when(rsUserMock.next()).thenReturn(true); - - Statement stmtMock = mock(Statement.class); - Mockito.when(connectionMock.createStatement()).thenReturn(stmtMock); - - ResultSet rsMock = getMockedResultSet(); - Mockito.when(stmtMock.executeQuery(anyString())).thenReturn(rsMock); - - // Run Test - Domains domains = domainStoreUnderTest.getDomains(); - - // Verify - assertTrue(domains.getDomains().size() == 1); - verify(stmtMock).close(); - } - - public ResultSet getMockedResultSet() throws SQLException { - ResultSet rsMock = mock(ResultSet.class); - Mockito.when(rsMock.next()).thenReturn(true).thenReturn(false); - Mockito.when(rsMock.getInt(DomainStore.SQL_ID)).thenReturn(1); - Mockito.when(rsMock.getString(DomainStore.SQL_NAME)).thenReturn("DomainName_1"); - Mockito.when(rsMock.getString(DomainStore.SQL_DESCR)).thenReturn("Desc_1"); - Mockito.when(rsMock.getInt(DomainStore.SQL_ENABLED)).thenReturn(1); - return rsMock; - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/GrantStoreTest.java b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/GrantStoreTest.java deleted file mode 100644 index 168b67e2..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/GrantStoreTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2014, 2016 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.opendaylight.aaa.api.model.Grants; - -public class GrantStoreTest { - - Connection connectionMock = mock(Connection.class); - private final GrantStore grantStoreUnderTest = new GrantStore(); - private String did = "5"; - private String uid = "5"; - - @Before - public void setup() { - grantStoreUnderTest.dbConnection = connectionMock; - } - - @Test - public void getGrantsTest() throws Exception { - // Setup Mock Behavior - String[] tableTypes = { "TABLE" }; - Mockito.when(connectionMock.isClosed()).thenReturn(false); - DatabaseMetaData dbmMock = mock(DatabaseMetaData.class); - Mockito.when(connectionMock.getMetaData()).thenReturn(dbmMock); - ResultSet rsUserMock = mock(ResultSet.class); - Mockito.when(dbmMock.getTables(null, null, "GRANTS", tableTypes)).thenReturn(rsUserMock); - Mockito.when(rsUserMock.next()).thenReturn(true); - - PreparedStatement pstmtMock = mock(PreparedStatement.class); - Mockito.when(connectionMock.prepareStatement(anyString())).thenReturn(pstmtMock); - - ResultSet rsMock = getMockedResultSet(); - Mockito.when(pstmtMock.executeQuery()).thenReturn(rsMock); - - // Run Test - Grants grants = grantStoreUnderTest.getGrants(did, uid); - - // Verify - assertTrue(grants.getGrants().size() == 1); - verify(pstmtMock).close(); - } - - public ResultSet getMockedResultSet() throws SQLException { - ResultSet rsMock = mock(ResultSet.class); - Mockito.when(rsMock.next()).thenReturn(true).thenReturn(false); - Mockito.when(rsMock.getInt(GrantStore.SQL_ID)).thenReturn(1); - Mockito.when(rsMock.getString(GrantStore.SQL_TENANTID)).thenReturn(did); - Mockito.when(rsMock.getString(GrantStore.SQL_USERID)).thenReturn(uid); - Mockito.when(rsMock.getString(GrantStore.SQL_ROLEID)).thenReturn("Role_1"); - - return rsMock; - - } - -} diff --git a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/H2StoreTest.java b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/H2StoreTest.java deleted file mode 100644 index f583a302..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/H2StoreTest.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import java.io.File; -import java.sql.SQLException; - -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.opendaylight.aaa.api.IDMStoreUtil; -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.User; - -public class H2StoreTest { - @BeforeClass - public static void start() { - File f = new File("idmlight.db.mv.db"); - if (f.exists()) { - f.delete(); - } - f = new File("idmlight.db.trace.db"); - if (f.exists()) { - f.delete(); - } - } - - @AfterClass - public static void end() { - File f = new File("idmlight.db.mv.db"); - if (f.exists()) { - f.delete(); - } - f = new File("idmlight.db.trace.db"); - if (f.exists()) { - f.delete(); - } - } - - @Before - public void before() throws StoreException, SQLException { - UserStore us = new UserStore(); - us.dbClean(); - DomainStore ds = new DomainStore(); - ds.dbClean(); - RoleStore rs = new RoleStore(); - rs.dbClean(); - GrantStore gs = new GrantStore(); - gs.dbClean(); - } - - @Test - public void testCreateDefaultDomain() throws StoreException { - Domain d = new Domain(); - Assert.assertEquals(true, d != null); - DomainStore ds = new DomainStore(); - d.setName(IIDMStore.DEFAULT_DOMAIN); - d.setEnabled(true); - d = ds.createDomain(d); - Assert.assertEquals(true, d != null); - } - - @Test - public void testCreateTempRole() throws StoreException { - Role role = H2Store.createRole("temp", "temp domain", "Temp Testing role"); - Assert.assertEquals(true, role != null); - } - - @Test - public void testCreateUser() throws StoreException { - User user = H2Store.createUser("test", "pass", "domain", "desc", "email", true, "SALT"); - Assert.assertEquals(true, user != null); - } - - @Test - public void testCreateGrant() throws StoreException { - Domain d = H2Store.createDomain("sdn", true); - Role role = H2Store.createRole("temp", "temp domain", "Temp Testing role"); - User user = H2Store.createUser("test", "pass", "domain", "desc", "email", true, "SALT"); - Grant g = H2Store.createGrant(d.getDomainid(), user.getUserid(), role.getRoleid()); - Assert.assertEquals(true, g != null); - } - - @Test - public void testUpdatingUserEmail() throws StoreException { - UserStore us = new UserStore(); - Domain d = H2Store.createDomain("sdn", true); - User user = H2Store.createUser("test", "pass", d.getDomainid(), "desc", "email", true, - "SALT"); - - user.setName("test"); - user = us.putUser(user); - Assert.assertEquals(true, user != null); - - user.setEmail("Test@Test.com"); - user = us.putUser(user); - - user = new User(); - user.setName("test"); - user.setDomainid(d.getDomainid()); - user = us.getUser(IDMStoreUtil.createUserid(user.getName(), user.getDomainid())); - - Assert.assertEquals("Test@Test.com", user.getEmail()); - } - /* - * @Test public void testCreateUserViaAPI() throws StoreException { Domain d - * = StoreBuilder.createDomain("sdn",true); - * - * User user = new User(); user.setName("Hello"); user.setPassword("Hello"); - * user.setDomainid(d.getDomainid()); UserHandler h = new UserHandler(); - * h.createUser(null, user); - * - * User u = new User(); u.setName("Hello"); u.setDomainid(d.getDomainid()); - * UserStore us = new UserStore(); u = - * us.getUser(IDMStoreUtil.createUserid(u.getName(),u.getDomainid())); - * - * Assert.assertEquals(true, u != null); } - * - * @Test public void testUpdateUserViaAPI() throws StoreException { Domain d - * = StoreBuilder.createDomain("sdn",true); - * - * User user = new User(); user.setName("Hello"); user.setPassword("Hello"); - * user.setDomainid(d.getDomainid()); UserHandler h = new UserHandler(); - * h.createUser(null, user); - * - * user.setEmail("Hello@Hello.com"); user.setPassword("Test123"); - * h.putUser(null, user, "" + user.getUserid()); - * - * UserStore us = new UserStore(); - * - * User u = new User(); u.setName("Hello"); u.setDomainid(d.getDomainid()); - * u = us.getUser(IDMStoreUtil.createUserid(u.getName(),u.getDomainid())); - * - * Assert.assertEquals("Hello@Hello.com", u.getEmail()); - * - * String hash = SHA256Calculator.getSHA256("Test123", u.getSalt()); - * Assert.assertEquals(u.getPassword(), hash); } - * - * @Test public void testUpdateUserRoleViaAPI() throws StoreException { - * Domain d = StoreBuilder.createDomain("sdn",true); Role role1 = - * StoreBuilder.createRole("temp1",d.getDomainid(),"Temp Testing role"); - * Role role2 = - * StoreBuilder.createRole("temp2",d.getDomainid(),"Temp Testing role"); - * - * User user = new User(); user.setName("Hello"); user.setPassword("Hello"); - * user.setDomainid(d.getDomainid()); - * - * UserHandler h = new UserHandler(); h.createUser(null, user); - * - * user.setEmail("Hello@Hello.com"); user.setPassword("Test123"); - * h.putUser(null, user, user.getUserid()); - * - * Grant g = new Grant(); g.setUserid(user.getUserid()); - * g.setDomainid(d.getDomainid()); g.setRoleid(role1.getRoleid()); - * GrantStore gs = new GrantStore(); g = gs.createGrant(g); - * - * Assert.assertEquals(true, g != null); Assert.assertEquals(g.getRoleid(), - * role1.getRoleid()); - * - * g = gs.deleteGrant(IDMStoreUtil.createGrantid(user.getUserid(), - * d.getDomainid(), role1.getRoleid())); g.setRoleid(role2.getRoleid()); g = - * gs.createGrant(g); - * - * Assert.assertEquals(true, g != null); Assert.assertEquals(g.getRoleid(), - * role2.getRoleid()); - * - * User u = new User(); u.setName("Hello"); u.setDomainid(d.getDomainid()); - * UserStore us = new UserStore(); u = - * us.getUser(IDMStoreUtil.createUserid(u.getName(),u.getDomainid())); - * - * Assert.assertEquals("Hello@Hello.com", u.getEmail()); - * - * String hash = SHA256Calculator.getSHA256("Test123", u.getSalt()); - * Assert.assertEquals(true, hash.equals(u.getPassword())); } - */ -} diff --git a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/RoleStoreTest.java b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/RoleStoreTest.java deleted file mode 100644 index 37cb17a6..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/RoleStoreTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.opendaylight.aaa.api.model.Roles; -import org.opendaylight.aaa.h2.persistence.RoleStore; - -public class RoleStoreTest { - - Connection connectionMock = mock(Connection.class); - private final RoleStore RoleStoreUnderTest = new RoleStore(); - - @Before - public void setup() { - RoleStoreUnderTest.dbConnection = connectionMock; - } - - @After - public void teardown() { - // dts.destroy(); - } - - @Test - public void getRolesTest() throws SQLException, Exception { - // Setup Mock Behavior - String[] tableTypes = { "TABLE" }; - Mockito.when(connectionMock.isClosed()).thenReturn(false); - DatabaseMetaData dbmMock = mock(DatabaseMetaData.class); - Mockito.when(connectionMock.getMetaData()).thenReturn(dbmMock); - ResultSet rsUserMock = mock(ResultSet.class); - Mockito.when(dbmMock.getTables(null, null, "ROLES", tableTypes)).thenReturn(rsUserMock); - Mockito.when(rsUserMock.next()).thenReturn(true); - - Statement stmtMock = mock(Statement.class); - Mockito.when(connectionMock.createStatement()).thenReturn(stmtMock); - - ResultSet rsMock = getMockedResultSet(); - Mockito.when(stmtMock.executeQuery(anyString())).thenReturn(rsMock); - - // Run Test - Roles roles = RoleStoreUnderTest.getRoles(); - - // Verify - assertTrue(roles.getRoles().size() == 1); - verify(stmtMock).close(); - - } - - public ResultSet getMockedResultSet() throws SQLException { - ResultSet rsMock = mock(ResultSet.class); - Mockito.when(rsMock.next()).thenReturn(true).thenReturn(false); - Mockito.when(rsMock.getInt(RoleStore.SQL_ID)).thenReturn(1); - Mockito.when(rsMock.getString(RoleStore.SQL_NAME)).thenReturn("RoleName_1"); - Mockito.when(rsMock.getString(RoleStore.SQL_DESCR)).thenReturn("Desc_1"); - return rsMock; - } -} diff --git a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/UserStoreTest.java b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/UserStoreTest.java deleted file mode 100644 index e214c261..00000000 --- a/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/UserStoreTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.h2.persistence; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.opendaylight.aaa.api.model.Users; -import org.opendaylight.aaa.h2.persistence.UserStore; - -public class UserStoreTest { - - Connection connectionMock = mock(Connection.class); - private final UserStore userStoreUnderTest = new UserStore(); - - @Before - public void setup() { - userStoreUnderTest.dbConnection = connectionMock; - } - - @After - public void teardown() { - // dts.destroy(); - } - - @Test - public void getUsersTest() throws SQLException, Exception { - // Setup Mock Behavior - String[] tableTypes = { "TABLE" }; - Mockito.when(connectionMock.isClosed()).thenReturn(false); - DatabaseMetaData dbmMock = mock(DatabaseMetaData.class); - Mockito.when(connectionMock.getMetaData()).thenReturn(dbmMock); - ResultSet rsUserMock = mock(ResultSet.class); - Mockito.when(dbmMock.getTables(null, null, "USERS", tableTypes)).thenReturn(rsUserMock); - Mockito.when(rsUserMock.next()).thenReturn(true); - - Statement stmtMock = mock(Statement.class); - Mockito.when(connectionMock.createStatement()).thenReturn(stmtMock); - - ResultSet rsMock = getMockedResultSet(); - Mockito.when(stmtMock.executeQuery(anyString())).thenReturn(rsMock); - - // Run Test - Users users = userStoreUnderTest.getUsers(); - - // Verify - assertTrue(users.getUsers().size() == 1); - verify(stmtMock).close(); - - } - - public ResultSet getMockedResultSet() throws SQLException { - ResultSet rsMock = mock(ResultSet.class); - Mockito.when(rsMock.next()).thenReturn(true).thenReturn(false); - Mockito.when(rsMock.getInt(UserStore.SQL_ID)).thenReturn(1); - Mockito.when(rsMock.getString(UserStore.SQL_NAME)).thenReturn("Name_1"); - Mockito.when(rsMock.getString(UserStore.SQL_EMAIL)).thenReturn("Name_1@company.com"); - Mockito.when(rsMock.getString(UserStore.SQL_PASSWORD)).thenReturn("Pswd_1"); - Mockito.when(rsMock.getString(UserStore.SQL_DESCR)).thenReturn("Desc_1"); - Mockito.when(rsMock.getInt(UserStore.SQL_ENABLED)).thenReturn(1); - return rsMock; - } -} diff --git a/odl-aaa-moon/aaa-idmlight/pom.xml b/odl-aaa-moon/aaa-idmlight/pom.xml deleted file mode 100644 index 5a86b0d1..00000000 --- a/odl-aaa-moon/aaa-idmlight/pom.xml +++ /dev/null @@ -1,229 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-idmlight - bundle - - - - - org.opendaylight.controller - config-api - ${config.version} - - - org.opendaylight.controller - sal-binding-config - - - org.opendaylight.controller - sal-binding-api - - - org.opendaylight.controller - sal-common-util - - - - org.opendaylight.aaa - aaa-authn-api - - - org.opendaylight.aaa - aaa-authn - - - org.slf4j - slf4j-api - - - com.sun.jersey - jersey-server - provided - - - javax.servlet - javax.servlet-api - provided - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - org.mockito - mockito-all - test - - - org.osgi - org.osgi.core - - - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.datatype - jackson-datatype-json-org - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-base - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-json-provider - - - com.fasterxml.jackson.module - jackson-module-jaxb-annotations - - - - org.eclipse.jetty - jetty-servlets - provided - - - - - com.sun.jersey.jersey-test-framework - jersey-test-framework-grizzly2 - test - - - junit - junit - test - - - org.slf4j - slf4j-simple - test - - - - - - - - org.opendaylight.yangtools - yang-maven-plugin - ${yangtools.version} - - - config - - generate-sources - - - - - org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator - ${jmxGeneratorPath} - - urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang - - - - org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl - ${salGeneratorPath} - - - true - - - - - - org.opendaylight.mdsal - maven-sal-api-gen-plugin - ${yangtools.version} - jar - - - org.opendaylight.controller - yang-jmx-generator-plugin - ${config.version} - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - - attach-artifact - - package - - - - ${project.build.directory}/classes/initial/08-aaa-idmlight-config.xml - xml - config - - - - - - attach-artifacts-idmtool - - attach-artifact - - package - - - - ${project.build.directory}/classes/idmtool.py - py - config - - - - - - - - - org.apache.felix - maven-bundle-plugin - - - true - - - org.opendaylight.aaa.shiro.realm,org.apache.shiro.web.env,org.apache.shiro.authc,org.opendaylight.aaa.shiro.web.env,org.opendaylight.aaa.shiro.filters,javax.servlet.http,javax.ws.rs,javax.ws.rs.core,javax.xml.bind.annotation,org.apache.felix.dm,org.opendaylight.aaa,org.opendaylight.aaa.api.*,org.osgi.framework,org.slf4j,org.eclipse.jetty.servlets,com.sun.jersey.spi.container.servlet,com.google.*,org.opendaylight.*,org.osgi.util.tracker - /auth - - - - ${project.basedir}/META-INF - - - - - - diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java deleted file mode 100644 index 6fcba5d6..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idm; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import javax.ws.rs.core.Application; - -import org.opendaylight.aaa.idm.rest.DomainHandler; -import org.opendaylight.aaa.idm.rest.RoleHandler; -import org.opendaylight.aaa.idm.rest.UserHandler; -import org.opendaylight.aaa.idm.rest.VersionHandler; - -/** - * A JAX-RS application for IdmLight. The REST endpoints delivered by this - * application are in the form: - * http://{HOST}:{PORT}/auth/v1/ - * - * For example, the users REST endpoint is: - * http://{HOST}:{PORT}/auth/v1/users - * - * This application is responsible for interaction with the backing h2 - * database store. - * - * @author liemmn - * @author Ryan Goulding (ryandgoulding@gmail.com) - * @see org.opendaylight.aaa.idm.rest.DomainHandler - * @see org.opendaylight.aaa.idm.rest.UserHandler - * @see org.opendaylight.aaa.idm.rest.RoleHandler - */ -public class IdmLightApplication extends Application { - - //TODO create a bug to address the fact that the implementation assumes 128 - // as the max length, even though this claims 256. - /** - * The maximum field length for identity fields. - */ - public static final int MAX_FIELD_LEN = 256; - public IdmLightApplication() { - } - - @Override - public Set> getClasses() { - return new HashSet>(Arrays.asList(VersionHandler.class, - DomainHandler.class, - RoleHandler.class, - UserHandler.class)); - } -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java deleted file mode 100644 index d17d2b13..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idm; - -import com.google.common.base.Preconditions; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.api.AuthenticationException; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.api.CredentialAuth; -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.api.IdMService; -import org.opendaylight.aaa.api.PasswordCredentials; -import org.opendaylight.aaa.api.SHA256Calculator; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Grants; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.aaa.api.model.Users; -import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An OSGi proxy for the IdmLight server. - * - */ -public class IdmLightProxy implements CredentialAuth, IdMService { - - private static final Logger LOG = LoggerFactory.getLogger(IdmLightProxy.class); - - /** - * claimCache is responsible for storing the active claims per domain. The - * outer map is keyed by domain, and the inner map is keyed by - * PasswordCredentials. - */ - private static Map> claimCache = new ConcurrentHashMap<>(); - - // adds a store for the default "sdn" domain - static { - claimCache.put(IIDMStore.DEFAULT_DOMAIN, - new ConcurrentHashMap()); - } - - @Override - public Claim authenticate(PasswordCredentials creds) { - Preconditions.checkNotNull(creds); - Preconditions.checkNotNull(creds.username()); - Preconditions.checkNotNull(creds.password()); - String domain = creds.domain() == null ? IIDMStore.DEFAULT_DOMAIN : creds.domain(); - // FIXME: Add cache invalidation - Map cache = claimCache.get(domain); - if (cache == null) { - cache = new ConcurrentHashMap(); - claimCache.put(domain, cache); - } - Claim claim = cache.get(creds); - if (claim == null) { - synchronized (claimCache) { - claim = cache.get(creds); - if (claim == null) { - claim = dbAuthenticate(creds); - if (claim != null) { - cache.put(creds, claim); - } - } - } - } - return claim; - } - - /** - * Clears the cache of any active claims. - */ - public static synchronized void clearClaimCache() { - LOG.info("Clearing the claim cache"); - for (Map cache : claimCache.values()) { - cache.clear(); - } - } - - private static Claim dbAuthenticate(PasswordCredentials creds) { - Domain domain = null; - User user = null; - String credsDomain = creds.domain() == null ? IIDMStore.DEFAULT_DOMAIN : creds.domain(); - // check to see domain exists - // TODO: ensure domain names are unique change to 'getDomain' - LOG.debug("get domain"); - try { - domain = AAAIDMLightModule.getStore().readDomain(credsDomain); - if (domain == null) { - throw new AuthenticationException("Domain :" + credsDomain + " does not exist"); - } - } catch (IDMStoreException e) { - throw new AuthenticationException("Error while fetching domain", e); - } - - // check to see user exists and passes cred check - try { - LOG.debug("check user / pwd"); - Users users = AAAIDMLightModule.getStore().getUsers(creds.username(), credsDomain); - List userList = users.getUsers(); - if (userList.size() == 0) { - throw new AuthenticationException("User :" + creds.username() - + " does not exist in domain " + credsDomain); - } - user = userList.get(0); - if (!SHA256Calculator.getSHA256(creds.password(), user.getSalt()).equals( - user.getPassword())) { - throw new AuthenticationException("UserName / Password not found"); - } - - // get all grants & roles for this domain and user - LOG.debug("get grants"); - List roles = new ArrayList(); - Grants grants = AAAIDMLightModule.getStore().getGrants(domain.getDomainid(), - user.getUserid()); - List grantList = grants.getGrants(); - for (int z = 0; z < grantList.size(); z++) { - Grant grant = grantList.get(z); - Role role = AAAIDMLightModule.getStore().readRole(grant.getRoleid()); - if (role != null) { - roles.add(role.getName()); - } - } - - // build up the claim - LOG.debug("build a claim"); - ClaimBuilder claim = new ClaimBuilder(); - claim.setUserId(user.getUserid().toString()); - claim.setUser(creds.username()); - claim.setDomain(credsDomain); - for (int z = 0; z < roles.size(); z++) { - claim.addRole(roles.get(z)); - } - return claim.build(); - } catch (IDMStoreException se) { - throw new AuthenticationException("idm data store exception :" + se.toString() + se); - } - } - - @Override - public List listDomains(String userId) { - LOG.debug("list Domains for userId: {}", userId); - List domains = new ArrayList(); - try { - Grants grants = AAAIDMLightModule.getStore().getGrants(userId); - List grantList = grants.getGrants(); - for (int z = 0; z < grantList.size(); z++) { - Grant grant = grantList.get(z); - Domain domain = AAAIDMLightModule.getStore().readDomain(grant.getDomainid()); - domains.add(domain.getName()); - } - return domains; - } catch (IDMStoreException se) { - LOG.warn("error getting domains ", se.toString(), se); - return domains; - } - - } - - @Override - public List listRoles(String userId, String domainName) { - LOG.debug("listRoles"); - List roles = new ArrayList(); - - try { - // find domain name for specied domain name - String did = null; - try { - Domain domain = AAAIDMLightModule.getStore().readDomain(domainName); - if (domain == null) { - LOG.debug("DomainName: {}", domainName + " Not found!"); - return roles; - } - did = domain.getDomainid(); - } catch (IDMStoreException e) { - return roles; - } - - // find all grants for uid and did - Grants grants = AAAIDMLightModule.getStore().getGrants(did, userId); - List grantList = grants.getGrants(); - for (int z = 0; z < grantList.size(); z++) { - Grant grant = grantList.get(z); - Role role = AAAIDMLightModule.getStore().readRole(grant.getRoleid()); - roles.add(role.getName()); - } - - return roles; - } catch (IDMStoreException se) { - LOG.warn("error getting roles ", se.toString(), se); - return roles; - } - } -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java deleted file mode 100644 index 111665c6..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idm; - -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.User; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * StoreBuilder is triggered during feature installation by - * AAAIDMLightModule.createInstance(). StoreBuilder is responsible - * for initializing the H2 database with initial default user account - * information. By default, the following users are created: - *
    - *
  1. admin
  2. - *
  3. user
  4. - *
- * - * By default, the following domain is created: - *
    - *
  1. sdn
  2. - *
- * - * By default, the following grants are created: - *
    - *
  1. admin with admin role on sdn
  2. - *
  3. admin with user role on sdn
  4. - *
  5. user with user role on sdn
  6. - *
- * - * @author peter.mellquist@hp.com - * @author saichler@cisco.com - */ -public class StoreBuilder { - - private static final Logger LOG = LoggerFactory.getLogger(StoreBuilder.class); - - public static void init(IIDMStore store) throws IDMStoreException { - LOG.info("creating idmlight schema in store"); - - // Check whether the default domain exists. If it exists, then do not - // create default data in the store. - // TODO Address the fact that someone may delete the sdn domain, or make - // sdn mandatory. - Domain defaultDomain = store.readDomain(IIDMStore.DEFAULT_DOMAIN); - if (defaultDomain != null) { - LOG.info("Found default domain in Store, skipping insertion of default data"); - return; - } - - // make domain - Domain domain = new Domain(); - User adminUser = new User(); - User userUser = new User(); - Role adminRole = new Role(); - Role userRole = new Role(); - domain.setEnabled(true); - domain.setName(IIDMStore.DEFAULT_DOMAIN); - domain.setDescription("default odl sdn domain"); - domain = store.writeDomain(domain); - - // Create default users - // "admin" user - adminUser.setEnabled(true); - adminUser.setName("admin"); - adminUser.setDomainid(domain.getDomainid()); - adminUser.setDescription("admin user"); - adminUser.setEmail(""); - adminUser.setPassword("admin"); - adminUser = store.writeUser(adminUser); - // "user" user - userUser.setEnabled(true); - userUser.setName("user"); - userUser.setDomainid(domain.getDomainid()); - userUser.setDescription("user user"); - userUser.setEmail(""); - userUser.setPassword("user"); - userUser = store.writeUser(userUser); - - // Create default Roles ("admin" and "user") - adminRole.setName("admin"); - adminRole.setDomainid(domain.getDomainid()); - adminRole.setDescription("a role for admins"); - adminRole = store.writeRole(adminRole); - userRole.setName("user"); - userRole.setDomainid(domain.getDomainid()); - userRole.setDescription("a role for users"); - userRole = store.writeRole(userRole); - - // Create default grants - Grant grant = new Grant(); - grant.setDomainid(domain.getDomainid()); - grant.setUserid(userUser.getUserid()); - grant.setRoleid(userRole.getRoleid()); - grant = store.writeGrant(grant); - - grant.setDomainid(domain.getDomainid()); - grant.setUserid(adminUser.getUserid()); - grant.setRoleid(userRole.getRoleid()); - grant = store.writeGrant(grant); - - grant.setDomainid(domain.getDomainid()); - grant.setUserid(adminUser.getUserid()); - grant.setRoleid(adminRole.getRoleid()); - grant = store.writeGrant(grant); - } -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java deleted file mode 100644 index 7ddc0748..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idm.rest; - -import java.util.ArrayList; -import java.util.List; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.model.Claim; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Domains; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Grants; -import org.opendaylight.aaa.api.model.IDMError; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.Roles; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.aaa.api.model.UserPwd; -import org.opendaylight.aaa.api.model.Users; -import org.opendaylight.aaa.idm.IdmLightProxy; -import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * REST application used to manipulate the H2 database domains table. The REST - * endpoint is /auth/v1/domains. - * - * The following provides examples of curl commands and payloads to utilize the - * domains REST endpoint: - * - * Get All Domains - * curl -u admin:admin http://{HOST}:{PORT}/auth/v1/domains - * - * Get A Specific Domain - * curl -u admin:admin http://{HOST}:{PORT}/auth/v1/domains/{id} - * - * Create A Domain - * curl -u admin:admin -X POST -H "Content-Type: application/json" --data-binary {@literal @}domain.json http://{HOST}:{PORT}/auth/v1/domains - * Example domain.json { - * "description": "new domain", - * "enabled", "true", - * "name", "not sdn" - * } - * - * Update A Domain - * curl -u admin:admin -X PUT -H "Content-Type: application/json" --data-binary {@literal @}domain.json http://{HOST}:{PORT}/auth/v1/domains - * Example domain.json { - * "description": "new domain description", - * "enabled", "true", - * "name", "not sdn" - * } - * - * @author peter.mellquist@hp.com - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -@Path("/v1/domains") -public class DomainHandler { - - private static final Logger LOG = LoggerFactory.getLogger(DomainHandler.class); - - /** - * Extracts all domains. - * - * @return a response with all domains stored in the H2 database - */ - @GET - @Produces("application/json") - public Response getDomains() { - LOG.info("Get /domains"); - Domains domains = null; - try { - domains = AAAIDMLightModule.getStore().getDomains(); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting domains"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - return Response.ok(domains).build(); - } - - /** - * Extracts the domain represented by domainId. - * - * @param domainId the string domain (i.e., "sdn") - * @return a response with the specified domain - */ - @GET - @Path("/{id}") - @Produces("application/json") - public Response getDomain(@PathParam("id") String domainId) { - LOG.info("Get /domains/{}", domainId); - Domain domain = null; - try { - domain = AAAIDMLightModule.getStore().readDomain(domainId); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting domain"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - - if (domain == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! domain id :" + domainId); - return Response.status(404).entity(idmerror).build(); - } - return Response.ok(domain).build(); - } - - /** - * Creates a domain. The name attribute is required for domain creation. - * Enabled and description fields are optional. Optional fields default - * in the following manner: - * enabled: false - * description: An empty string (""). - * - * @param info passed from Jersey - * @param domain designated by the REST payload - * @return A response stating success or failure of domain creation. - */ - @POST - @Consumes("application/json") - @Produces("application/json") - public Response createDomain(@Context UriInfo info, Domain domain) { - LOG.info("Post /domains"); - try { - if (domain.isEnabled() == null) { - domain.setEnabled(false); - } - if (domain.getName() == null) { - domain.setName(""); - } - if (domain.getDescription() == null) { - domain.setDescription(""); - } - domain = AAAIDMLightModule.getStore().writeDomain(domain); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error creating domain"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - return Response.status(201).entity(domain).build(); - } - - /** - * Updates a domain. - * - * @param info passed from Jersey - * @param domain the REST payload - * @param domainId the last part of the path, containing the specified domain id - * @return A response stating success or failure of domain update. - */ - @PUT - @Path("/{id}") - @Consumes("application/json") - @Produces("application/json") - public Response putDomain(@Context UriInfo info, Domain domain, @PathParam("id") String domainId) { - LOG.info("Put /domains/{}", domainId); - try { - domain.setDomainid(domainId); - domain = AAAIDMLightModule.getStore().updateDomain(domain); - if (domain == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! Domain id :" + domainId); - return Response.status(404).entity(idmerror).build(); - } - IdmLightProxy.clearClaimCache(); - return Response.status(200).entity(domain).build(); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error putting domain"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - } - - /** - * Deletes a domain. - * - * @param info passed from Jersey - * @param domainId the last part of the path, containing the specified domain id - * @return A response stating success or failure of domain deletion. - */ - @DELETE - @Path("/{id}") - public Response deleteDomain(@Context UriInfo info, @PathParam("id") String domainId) { - LOG.info("Delete /domains/{}", domainId); - - try { - Domain domain = AAAIDMLightModule.getStore().deleteDomain(domainId); - if (domain == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! Domain id :" + domainId); - return Response.status(404).entity(idmerror).build(); - } - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error deleting Domain"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - IdmLightProxy.clearClaimCache(); - return Response.status(204).build(); - } - - /** - * Creates a grant. A grant defines the role a particular user is given on - * a particular domain. For example, by default, AAA installs a grant for - * the "admin" user, granting permission to act with "admin" role on the - * "sdn" domain. - * - * @param info passed from Jersey - * @param domainId the domain the user is allowed to access - * @param userId the user that is allowed to access the domain - * @param grant the payload containing role access controls - * @return A response stating success or failure of grant creation. - */ - @POST - @Path("/{did}/users/{uid}/roles") - @Consumes("application/json") - @Produces("application/json") - public Response createGrant(@Context UriInfo info, @PathParam("did") String domainId, - @PathParam("uid") String userId, Grant grant) { - LOG.info("Post /domains/{}/users/{}/roles", domainId, userId); - Domain domain = null; - User user = null; - Role role = null; - String roleId = null; - - // validate domain id - try { - domain = AAAIDMLightModule.getStore().readDomain(domainId); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting domain"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (domain == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! domain id :" + domainId); - return Response.status(404).entity(idmerror).build(); - } - grant.setDomainid(domainId); - - try { - user = AAAIDMLightModule.getStore().readUser(userId); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting user"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (user == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! User id :" + userId); - return Response.status(404).entity(idmerror).build(); - } - grant.setUserid(userId); - - // validate role id - try { - roleId = grant.getRoleid(); - LOG.info("roleid = {}", roleId); - } catch (NumberFormatException nfe) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Invalid Role id :" + grant.getRoleid()); - return Response.status(404).entity(idmerror).build(); - } - try { - role = AAAIDMLightModule.getStore().readRole(roleId); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting role"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (role == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! role :" + grant.getRoleid()); - return Response.status(404).entity(idmerror).build(); - } - - // see if grant already exists for this - try { - Grant existingGrant = AAAIDMLightModule.getStore().readGrant(domainId, userId, roleId); - if (existingGrant != null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Grant already exists for did:" + domainId + " uid:" + userId - + " rid:" + roleId); - return Response.status(403).entity(idmerror).build(); - } - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error creating grant"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - - // create grant - try { - grant = AAAIDMLightModule.getStore().writeGrant(grant); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error creating grant"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - - IdmLightProxy.clearClaimCache(); - return Response.status(201).entity(grant).build(); - } - - /** - * Used to validate user access. - * - * @param info passed from Jersey - * @param domainId the domain in question - * @param userpwd the password attempt - * @return A response stating success or failure of user validation. - */ - @POST - @Path("/{did}/users/roles") - @Consumes("application/json") - @Produces("application/json") - public Response validateUser(@Context UriInfo info, @PathParam("did") String domainId, - UserPwd userpwd) { - - LOG.info("GET /domains/{}/users", domainId); - Domain domain = null; - Claim claim = new Claim(); - List roleList = new ArrayList(); - - try { - domain = AAAIDMLightModule.getStore().readDomain(domainId); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting domain"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (domain == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! Domain id :" + domainId); - return Response.status(404).entity(idmerror).build(); - } - - // check request body for username and pwd - String username = userpwd.getUsername(); - if (username == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("username not specfied in request body"); - return Response.status(400).entity(idmerror).build(); - } - String pwd = userpwd.getUserpwd(); - if (pwd == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("userpwd not specfied in request body"); - return Response.status(400).entity(idmerror).build(); - } - - // find userid for user - try { - Users users = AAAIDMLightModule.getStore().getUsers(username, domainId); - List userList = users.getUsers(); - if (userList.size() == 0) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("did not find username: " + username); - return Response.status(404).entity(idmerror).build(); - } - User user = userList.get(0); - String userPwd = user.getPassword(); - String reqPwd = userpwd.getUserpwd(); - if (!userPwd.equals(reqPwd)) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("password does not match for username: " + username); - return Response.status(401).entity(idmerror).build(); - } - claim.setDomainid(domainId); - claim.setUsername(username); - claim.setUserid(user.getUserid()); - try { - Grants grants = AAAIDMLightModule.getStore().getGrants(domainId, user.getUserid()); - List grantsList = grants.getGrants(); - for (int i = 0; i < grantsList.size(); i++) { - Grant grant = grantsList.get(i); - Role role = AAAIDMLightModule.getStore().readRole(grant.getRoleid()); - roleList.add(role); - } - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting Roles"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - claim.setRoles(roleList); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting user"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - - return Response.ok(claim).build(); - } - - /** - * Get the grants for a user on a domain. - * - * @param info passed from Jersey - * @param domainId the domain in question - * @param userId the user in question - * @return A response containing the grants for a user on a domain. - */ - @GET - @Path("/{did}/users/{uid}/roles") - @Produces("application/json") - public Response getRoles(@Context UriInfo info, @PathParam("did") String domainId, - @PathParam("uid") String userId) { - LOG.info("GET /domains/{}/users/{}/roles", domainId, userId); - Domain domain = null; - User user = null; - Roles roles = new Roles(); - List roleList = new ArrayList(); - - try { - domain = AAAIDMLightModule.getStore().readDomain(domainId); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting domain"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (domain == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! Domain id :" + domainId); - return Response.status(404).entity(idmerror).build(); - } - - try { - user = AAAIDMLightModule.getStore().readUser(userId); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting user"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (user == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! User id :" + userId); - return Response.status(404).entity(idmerror).build(); - } - - try { - Grants grants = AAAIDMLightModule.getStore().getGrants(domainId, userId); - List grantsList = grants.getGrants(); - for (int i = 0; i < grantsList.size(); i++) { - Grant grant = grantsList.get(i); - Role role = AAAIDMLightModule.getStore().readRole(grant.getRoleid()); - roleList.add(role); - } - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting Roles"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - - roles.setRoles(roleList); - return Response.ok(roles).build(); - } - - /** - * Delete a grant. - * - * @param info passed from Jersey - * @param domainId the domain for the grant - * @param userId the user for the grant - * @param roleId the role for the grant - * @return A response stating success or failure of the grant deletion. - */ - @DELETE - @Path("/{did}/users/{uid}/roles/{rid}") - public Response deleteGrant(@Context UriInfo info, @PathParam("did") String domainId, - @PathParam("uid") String userId, @PathParam("rid") String roleId) { - Domain domain = null; - User user = null; - Role role = null; - - try { - domain = AAAIDMLightModule.getStore().readDomain(domainId); - } catch (IDMStoreException se) { - LOG.error("Error deleting Grant : ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting domain"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (domain == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! Domain id :" + domainId); - return Response.status(404).entity(idmerror).build(); - } - - try { - user = AAAIDMLightModule.getStore().readUser(userId); - } catch (IDMStoreException se) { - LOG.error("StoreException : ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting user"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (user == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! User id :" + userId); - return Response.status(404).entity(idmerror).build(); - } - - try { - role = AAAIDMLightModule.getStore().readRole(roleId); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error getting Role"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - if (role == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Not found! Role id :" + roleId); - return Response.status(404).entity(idmerror).build(); - } - - // see if grant already exists - try { - Grant existingGrant = AAAIDMLightModule.getStore().readGrant(domainId, userId, roleId); - if (existingGrant == null) { - IDMError idmerror = new IDMError(); - idmerror.setMessage("Grant does not exist for did:" + domainId + " uid:" + userId - + " rid:" + roleId); - return Response.status(404).entity(idmerror).build(); - } - existingGrant = AAAIDMLightModule.getStore().deleteGrant(existingGrant.getGrantid()); - } catch (IDMStoreException se) { - LOG.error("StoreException: ", se); - IDMError idmerror = new IDMError(); - idmerror.setMessage("Internal error creating grant"); - idmerror.setDetails(se.getMessage()); - return Response.status(500).entity(idmerror).build(); - } - IdmLightProxy.clearClaimCache(); - return Response.status(204).build(); - } - -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java deleted file mode 100644 index 34a60c0c..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idm.rest; - -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; - -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.model.IDMError; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.Roles; -import org.opendaylight.aaa.idm.IdmLightApplication; -import org.opendaylight.aaa.idm.IdmLightProxy; -import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * REST application used to manipulate the H2 database roles table. The REST - * endpoint is /auth/v1/roles. - * - * The following provides examples of curl commands and payloads to utilize the - * roles REST endpoint: - * - * Get All Roles - * curl -u admin:admin http://{HOST}:{PORT}/auth/v1/roles - * - * Get A Specific Role - * curl -u admin:admin http://{HOST}:{PORT}/auth/v1/roles/{id} - * - * Create A Role - * curl -u admin:admin -X POST -H "Content-Type: application/json" --data-binary {@literal @}role.json http://{HOST}:{PORT}/auth/v1/roles - * An example of role.json: - * { - * "name":"IT Administrator", - * "description":"A user role for IT admins" - * } - * - * Update A Role - * curl -u admin:admin -X PUT -H "Content-Type: application/json" --data-binary {@literal @}role.json http://{HOST}:{PORT}/auth/v1/roles/{id} - * An example of role.json: - * { - * "name":"IT Administrator Limited", - * "description":"A user role for IT admins who can only do one thing" - * } - * - * @author peter.mellquist@hp.com - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -@Path("/v1/roles") -public class RoleHandler { - private static final Logger LOG = LoggerFactory.getLogger(RoleHandler.class); - - /** - * Extracts all roles. - * - * @return A response with all roles in the H2 database, or internal error if one is encountered - */ - @GET - @Produces("application/json") - public Response getRoles() { - LOG.info("get /roles"); - Roles roles = null; - try { - roles = AAAIDMLightModule.getStore().getRoles(); - } catch (IDMStoreException se) { - return new IDMError(500, "internal error getting roles", se.getMessage()).response(); - } - return Response.ok(roles).build(); - } - - /** - * Extract a specific role identified by id - * - * @param id the String id for the role - * @return A response with the role identified by id, or internal error if one is encountered - */ - @GET - @Path("/{id}") - @Produces("application/json") - public Response getRole(@PathParam("id") String id) { - LOG.info("get /roles/{}", id); - Role role = null; - - try { - role = AAAIDMLightModule.getStore().readRole(id); - } catch (IDMStoreException se) { - return new IDMError(500, "internal error getting roles", se.getMessage()).response(); - } - - if (role == null) { - return new IDMError(404, "role not found id :" + id, "").response(); - } - return Response.ok(role).build(); - } - - /** - * Creates a role. - * - * @param info passed from Jersey - * @param role the role JSON payload - * @return A response stating success or failure of role creation, or internal error if one is encountered - */ - @POST - @Consumes("application/json") - @Produces("application/json") - public Response createRole(@Context UriInfo info, Role role) { - LOG.info("Post /roles"); - try { - // TODO: role names should be unique! - // name - if (role.getName() == null) { - return new IDMError(404, "name must be defined on role create", "").response(); - } else if (role.getName().length() > IdmLightApplication.MAX_FIELD_LEN) { - return new IDMError(400, "role name max length is :" - + IdmLightApplication.MAX_FIELD_LEN, "").response(); - } - - // domain - if (role.getDomainid() == null) { - return new IDMError(404, - "The role's domain must be defined on role when creating a role.", "") - .response(); - } else if (role.getDomainid().length() > IdmLightApplication.MAX_FIELD_LEN) { - return new IDMError(400, "role domain max length is :" - + IdmLightApplication.MAX_FIELD_LEN, "").response(); - } - - // description - if (role.getDescription() == null) { - role.setDescription(""); - } else if (role.getDescription().length() > IdmLightApplication.MAX_FIELD_LEN) { - return new IDMError(400, "role description max length is :" - + IdmLightApplication.MAX_FIELD_LEN, "").response(); - } - - role = AAAIDMLightModule.getStore().writeRole(role); - } catch (IDMStoreException se) { - return new IDMError(500, "internal error creating role", se.getMessage()).response(); - } - - return Response.status(201).entity(role).build(); - } - - /** - * Updates a specific role identified by id. - * - * @param info passed from Jersey - * @param role the role JSON payload - * @param id the String id for the role - * @return A response stating success or failure of role update, or internal error if one occurs - */ - @PUT - @Path("/{id}") - @Consumes("application/json") - @Produces("application/json") - public Response putRole(@Context UriInfo info, Role role, @PathParam("id") String id) { - LOG.info("put /roles/{}", id); - - try { - role.setRoleid(id); - - // name - // TODO: names should be unique - if ((role.getName() != null) - && (role.getName().length() > IdmLightApplication.MAX_FIELD_LEN)) { - return new IDMError(400, "role name max length is :" - + IdmLightApplication.MAX_FIELD_LEN, "").response(); - } - - // description - if ((role.getDescription() != null) - && (role.getDescription().length() > IdmLightApplication.MAX_FIELD_LEN)) { - return new IDMError(400, "role description max length is :" - + IdmLightApplication.MAX_FIELD_LEN, "").response(); - } - - role = AAAIDMLightModule.getStore().updateRole(role); - if (role == null) { - return new IDMError(404, "role id not found :" + id, "").response(); - } - IdmLightProxy.clearClaimCache(); - return Response.status(200).entity(role).build(); - } catch (IDMStoreException se) { - return new IDMError(500, "internal error putting role", se.getMessage()).response(); - } - } - - /** - * Delete a role. - * - * @param info passed from Jersey - * @param id the String id for the role - * @return A response stating success or failure of user deletion, or internal error if one occurs - */ - @DELETE - @Path("/{id}") - public Response deleteRole(@Context UriInfo info, @PathParam("id") String id) { - LOG.info("Delete /roles/{}", id); - - try { - Role role = AAAIDMLightModule.getStore().deleteRole(id); - if (role == null) { - return new IDMError(404, "role id not found :" + id, "").response(); - } - } catch (IDMStoreException se) { - return new IDMError(500, "internal error deleting role", se.getMessage()).response(); - } - IdmLightProxy.clearClaimCache(); - return Response.status(204).build(); - } - -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java deleted file mode 100644 index 24fefd7b..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idm.rest; - -import java.util.Collection; - -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; - -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.model.IDMError; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.aaa.api.model.Users; -import org.opendaylight.aaa.idm.IdmLightApplication; -import org.opendaylight.aaa.idm.IdmLightProxy; -import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * REST application used to manipulate the H2 database users table. The REST - * endpoint is /auth/v1/users. - * - * The following provides examples of how curl commands and payloads to utilize - * the users REST endpoint: - * - * Get All Users - * curl -u admin:admin http://{HOST}:{PORT}/auth/v1/users - * - * Get A Specific User - * curl -u admin:admin http://{HOST}:{PORT}/auth/v1/users/{id} - * - * Create A User - * curl -u admin:admin -X POST -H "Content-type: application/json" --data-binary {@literal @}user.json http://{HOST}:{PORT}/auth/v1/users - * An example of user.json file is: - * { - * "name": "admin2", - * "password", "admin2", - * "domain": "sdn" - * } - * - * Update A User - * curl -u admin:admin -X PUT -H "Content-type: application/json" --data-binary {@literal @}user.json http://{HOST}:{PORT}/auth/v1/users/{id} - * An example of user.json file is: - * { - * "name": "admin2", - * "password", "admin2", - * "domain": "sdn", - * "description", "Simple description." - * } - * - * Delete A User - * curl -u admin:admin -X DELETE http://{HOST}:{PORT}/auth/v1/users/{id} - * - * @author peter.mellquist@hp.com - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -@Path("/v1/users") -public class UserHandler { - - private static final Logger LOG = LoggerFactory.getLogger(UserHandler.class); - - /** - * If a user is created through the /auth/v1/users rest - * endpoint without a password, the default password is assigned to the - * user. - */ - private final static String DEFAULT_PWD = "changeme"; - - /** - * When an HTTP GET is performed on /auth/v1/users, the - * password field is replaced with REDACTED_PASSWORD for - * security reasons. - */ - private static final String REDACTED_PASSWORD = "**********"; - - /** - * When an HTTP GET is performed on /auth/v1/users, the salt - * field is replaced with REDACTED_SALT for security reasons. - */ - private static final String REDACTED_SALT = "**********"; - - /** - * When creating a user, the description is optional and defaults to an - * empty string. - */ - private static final String DEFAULT_DESCRIPTION = ""; - - /** - * When creating a user, the email is optional and defaults to an empty - * string. - */ - private static final String DEFAULT_EMAIL = ""; - - /** - * Extracts all users. The password and salt fields are redacted for - * security reasons. - * - * @return A response containing the users, or internal error if one occurs - */ - @GET - @Produces("application/json") - public Response getUsers() { - LOG.info("GET /auth/v1/users (extracts all users)"); - - try { - final Users users = AAAIDMLightModule.getStore().getUsers(); - - // Redact the password and salt for security purposes. - final Collection usersList = users.getUsers(); - for (User user : usersList) { - redactUserPasswordInfo(user); - } - - return Response.ok(users).build(); - } catch (IDMStoreException se) { - return internalError("getting", se); - } - } - - /** - * Extracts the user represented by id. The password and salt - * fields are redacted for security reasons. - * - * @param id the unique id of representing the user account - * @return A response with the user information, or internal error if one occurs - */ - @GET - @Path("/{id}") - @Produces("application/json") - public Response getUser(@PathParam("id") String id) { - LOG.info("GET auth/v1/users/ {} (extract user with specified id)", id); - - try { - final User user = AAAIDMLightModule.getStore().readUser(id); - - if (user == null) { - return new IDMError(404, String.format("user not found! id: %d", id), "").response(); - } - - // Redact the password and salt for security purposes. - redactUserPasswordInfo(user); - - return Response.ok(user).build(); - } catch (IDMStoreException se) { - return internalError("getting", se); - } - } - - /** - * REST endpoint to create a user. Name and domain are required attributes, - * and all other fields (description, email, password, enabled) are - * optional. Optional fields default in the following manner: - * description: An empty string (""). - * email: An empty string (""). - * password: changeme enabled: - * true - * - * If a password is not provided, please ensure you change the default - * password ASAP for security reasons! - * - * @param info passed from Jersey - * @param user the user defined in the JSON payload - * @return A response stating success or failure of user creation - */ - @POST - @Consumes("application/json") - @Produces("application/json") - public Response createUser(@Context UriInfo info, User user) { - LOG.info("POST /auth/v1/users (create a user with the specified payload"); - - // The "enabled" field is optional, and defaults to true. - if (user.isEnabled() == null) { - user.setEnabled(true); - } - - // The "name" field is required. - final String userName = user.getName(); - if (userName == null) { - return missingRequiredField("name"); - } - // The "name" field has a maximum length. - if (userName.length() > IdmLightApplication.MAX_FIELD_LEN) { - return providedFieldTooLong("name", IdmLightApplication.MAX_FIELD_LEN); - } - - // The "domain field is required. - final String domainId = user.getDomainid(); - if (domainId == null) { - return missingRequiredField("domain"); - } - // The "domain" field has a maximum length. - if (domainId.length() > IdmLightApplication.MAX_FIELD_LEN) { - return providedFieldTooLong("domain", IdmLightApplication.MAX_FIELD_LEN); - } - - // The "description" field is optional and defaults to "". - final String userDescription = user.getDescription(); - if (userDescription == null) { - user.setDescription(DEFAULT_DESCRIPTION); - } - // The "description" field has a maximum length. - if (userDescription.length() > IdmLightApplication.MAX_FIELD_LEN) { - return providedFieldTooLong("description", IdmLightApplication.MAX_FIELD_LEN); - } - - // The "email" field is optional and defaults to "". - final String userEmail = user.getEmail(); - if (userEmail == null) { - user.setEmail(DEFAULT_EMAIL); - } - if (userEmail.length() > IdmLightApplication.MAX_FIELD_LEN) { - return providedFieldTooLong("email", IdmLightApplication.MAX_FIELD_LEN); - } - // TODO add a check on email format here. - - // The "password" field is optional and defautls to "changeme". - final String userPassword = user.getPassword(); - if (userPassword == null) { - user.setPassword(DEFAULT_PWD); - } else if (userPassword.length() > IdmLightApplication.MAX_FIELD_LEN) { - return providedFieldTooLong("password", IdmLightApplication.MAX_FIELD_LEN); - } - - try { - // At this point, fields have been properly verified. Create the - // user account - final User createdUser = AAAIDMLightModule.getStore().writeUser(user); - user.setUserid(createdUser.getUserid()); - } catch (IDMStoreException se) { - return internalError("creating", se); - } - - // Redact the password and salt for security reasons. - redactUserPasswordInfo(user); - // TODO report back to the client a warning message to change the - // default password if none was specified. - return Response.status(201).entity(user).build(); - } - - /** - * REST endpoint to update a user account. - * - * @param info passed from Jersey - * @param user the user defined in the JSON payload - * @param id the unique id for the user that will be updated - * @return A response stating success or failure of the user update - */ - @PUT - @Path("/{id}") - @Consumes("application/json") - @Produces("application/json") - public Response putUser(@Context UriInfo info, User user, @PathParam("id") String id) { - - LOG.info("PUT /auth/v1/users/{} (Updates a user account)", id); - - try { - user.setUserid(id); - - if (checkInputFieldLength(user.getPassword())) { - return providedFieldTooLong("password", IdmLightApplication.MAX_FIELD_LEN); - } - - if (checkInputFieldLength(user.getName())) { - return providedFieldTooLong("name", IdmLightApplication.MAX_FIELD_LEN); - } - - if (checkInputFieldLength(user.getDescription())) { - return providedFieldTooLong("description", IdmLightApplication.MAX_FIELD_LEN); - } - - if (checkInputFieldLength(user.getEmail())) { - return providedFieldTooLong("email", IdmLightApplication.MAX_FIELD_LEN); - } - - if (checkInputFieldLength(user.getDomainid())) { - return providedFieldTooLong("domain", IdmLightApplication.MAX_FIELD_LEN); - } - - user = AAAIDMLightModule.getStore().updateUser(user); - if (user == null) { - return new IDMError(404, String.format("User not found for id %s", id), "").response(); - } - - IdmLightProxy.clearClaimCache(); - - // Redact the password and salt for security reasons. - redactUserPasswordInfo(user); - return Response.status(200).entity(user).build(); - } catch (IDMStoreException se) { - return internalError("updating", se); - } - } - - /** - * REST endpoint to delete a user account. - * - * @param info passed from Jersey - * @param id the unique id of the user which is being deleted - * @return A response stating success or failure of user deletion - */ - @DELETE - @Path("/{id}") - public Response deleteUser(@Context UriInfo info, @PathParam("id") String id) { - LOG.info("DELETE /auth/v1/users/{} (Delete a user account)", id); - - try { - final User user = AAAIDMLightModule.getStore().deleteUser(id); - - if (user == null) { - return new IDMError(404, - String.format("Error deleting user. " + - "Couldn't find user with id %s", id), - "").response(); - } - } catch (IDMStoreException se) { - return internalError("deleting", se); - } - - // Successfully deleted the user; report success to the client. - IdmLightProxy.clearClaimCache(); - return Response.status(204).build(); - } - - /** - * Creates a Response related to an internal server error. - * - * @param verbal such as "creating", "deleting", "updating" - * @param e The exception, which is propagated in the response - * @return A response containing internal error with specific reasoning - */ - private Response internalError(final String verbal, final Exception e) { - LOG.error("There was an internal error {} the user", verbal, e); - return new IDMError(500, - String.format("There was an internal error %s the user", verbal), - e.getMessage()).response(); - } - - /** - * Creates a Response related to the user not providing a - * required field. - * - * @param fieldName the name of the field which is missing - * @return A response explaining that the request is missing a field - */ - private Response missingRequiredField(final String fieldName) { - - return new IDMError(400, - String.format("%s is required to create the user account. " + - "Please provide a %s in your payload.", fieldName, fieldName), - "").response(); - } - - /** - * Creates a Response related to the user providing a field - * that is too long. - * - * @param fieldName the name of the field that is too long - * @param maxFieldLength the maximum length of fieldName - * @return A response containing the bad field and the maximum field length - */ - private Response providedFieldTooLong(final String fieldName, final int maxFieldLength) { - - return new IDMError(400, - getProvidedFieldTooLongMessage(fieldName, maxFieldLength), - "").response(); - } - - /** - * Creates the client-facing message related to the user providing a field - * that is too long. - * - * @param fieldName the name of the field that is too long - * @param maxFieldLength the maximum length of fieldName - * @return - */ - private static String getProvidedFieldTooLongMessage(final String fieldName, - final int maxFieldLength) { - - return String.format("The provided {} field is too long. " + - "The max length is {}.", fieldName, maxFieldLength); - } - - /** - * Prepares a user account for output by redacting the appropriate fields. - * This method side-effects the user parameter. - * - * @param user the user account which will have fields redacted - */ - private static void redactUserPasswordInfo(final User user) { - user.setPassword(REDACTED_PASSWORD); - user.setSalt(REDACTED_SALT); - } - - /** - * Validate the input field length - * - * @param inputField - * @return true if input field bigger than the MAX_FIELD_LEN - */ - private boolean checkInputFieldLength(final String inputField) { - return inputField != null && (inputField.length() > IdmLightApplication.MAX_FIELD_LEN); - } -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java deleted file mode 100644 index f865162a..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idm.rest; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; - -import org.opendaylight.aaa.api.model.Version; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author peter.mellquist@hp.com - * - */ -@Deprecated -@Path("/") -public class VersionHandler { - private static final Logger LOG = LoggerFactory.getLogger(VersionHandler.class);; - - protected static String CURRENT_VERSION = "v1"; - protected static String LAST_UPDATED = "2014-04-18T18:30:02.25Z"; - protected static String CURRENT_STATUS = "CURRENT"; - - @GET - @Produces("application/json") - public Version getVersion(@Context HttpServletRequest request) { - LOG.info("Get /"); - Version version = new Version(); - version.setId(CURRENT_VERSION); - version.setUpdated(LAST_UPDATED); - version.setStatus(CURRENT_STATUS); - return version; - } - -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java deleted file mode 100644 index d6872635..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204; - -import org.opendaylight.aaa.api.CredentialAuth; -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.api.IdMService; -import org.opendaylight.aaa.idm.IdmLightProxy; -import org.opendaylight.aaa.idm.StoreBuilder; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.ServiceRegistration; -import org.osgi.util.tracker.ServiceTracker; -import org.osgi.util.tracker.ServiceTrackerCustomizer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class AAAIDMLightModule extends org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AbstractAAAIDMLightModule { - - private static final Logger LOG = LoggerFactory.getLogger(AAAIDMLightModule.class); - private BundleContext bundleContext = null; - private static volatile IIDMStore store = null; - - public AAAIDMLightModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { - super(identifier, dependencyResolver); - } - - public AAAIDMLightModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule oldModule, java.lang.AutoCloseable oldInstance) { - super(identifier, dependencyResolver, oldModule, oldInstance); - } - - @Override - public void customValidation() { - // add custom validation form module attributes here. - } - - @Override - public java.lang.AutoCloseable createInstance() { - final IdmLightProxy proxy = new IdmLightProxy(); - final ServiceRegistration idmService = bundleContext.registerService(IdMService.class.getName(), proxy, null); - final ServiceRegistration clientAuthService = bundleContext.registerService(CredentialAuth.class.getName(), proxy, null); - - final ServiceTracker storeServiceTracker = new ServiceTracker<>(bundleContext, IIDMStore.class, - new ServiceTrackerCustomizer() { - @Override - public IIDMStore addingService(ServiceReference reference) { - store = reference.getBundle().getBundleContext().getService(reference); - LOG.info("IIDMStore service {} was found", store.getClass()); - try { - StoreBuilder.init(store); - } catch (IDMStoreException e) { - LOG.error("Failed to initialize data in store", e); - } - - return store; - } - - @Override - public void modifiedService(ServiceReference reference, IIDMStore service) { - } - - @Override - public void removedService(ServiceReference reference, IIDMStore service) { - } - }); - - storeServiceTracker.open(); - - LOG.info("AAA IDM Light Module Initialized"); - return new AutoCloseable() { - @Override - public void close() throws Exception { - idmService.unregister(); - clientAuthService.unregister(); - storeServiceTracker.close(); - } - }; - } - - public void setBundleContext(BundleContext b){ - this.bundleContext = b; - } - - public static final IIDMStore getStore(){ - return store; - } - - public static final void setStore(IIDMStore s){ - store = s; - } -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java deleted file mode 100644 index de277da8..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* -* Generated file -* -* Generated from: yang module name: aaa-idmlight yang module local name: aaa-idmlight -* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator -* Generated at: Fri Dec 04 11:37:37 PST 2015 -* -* Do not modify this file unless it is present under src/main directory -*/ -package org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204; - -import org.opendaylight.controller.config.api.DependencyResolver; -import org.osgi.framework.BundleContext; - -public class AAAIDMLightModuleFactory extends org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AbstractAAAIDMLightModuleFactory { - @Override - public AAAIDMLightModule instantiateModule(String instanceName, DependencyResolver dependencyResolver, AAAIDMLightModule oldModule, AutoCloseable oldInstance, BundleContext bundleContext) { - AAAIDMLightModule module = super.instantiateModule(instanceName, dependencyResolver, oldModule, oldInstance, bundleContext); - module.setBundleContext(bundleContext); - return module; - } - - @Override - public AAAIDMLightModule instantiateModule(String instanceName, DependencyResolver dependencyResolver, BundleContext bundleContext) { - AAAIDMLightModule module = super.instantiateModule(instanceName, dependencyResolver, bundleContext); - module.setBundleContext(bundleContext); - return module; - } -} diff --git a/odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml b/odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml deleted file mode 100644 index 9a19155a..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - IdmLight - com.sun.jersey.spi.container.servlet.ServletContainer - - javax.ws.rs.Application - org.opendaylight.aaa.idm.IdmLightApplication - - - com.sun.jersey.api.json.POJOMappingFeaturetrue - - 1 - - - - IdmLight - /* - - - - - shiroEnvironmentClass - org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment - - - - org.apache.shiro.web.env.EnvironmentLoaderListener - - - - ShiroFilter - org.opendaylight.aaa.shiro.filters.AAAFilter - - - - ShiroFilter - /* - - - - cross-origin-restconf - org.eclipse.jetty.servlets.CrossOriginFilter - - allowedOrigins - * - - - allowedMethods - GET,POST,OPTIONS,DELETE,PUT,HEAD - - - allowedHeaders - origin, content-type, accept, authorization, Authorization - - - - - cross-origin-restconf - /* - - - - - NB api - /* - POST - GET - PUT - PATCH - DELETE - HEAD - - - - \ No newline at end of file diff --git a/odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py b/odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py deleted file mode 100644 index d0a31ba2..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env python - -# -# Copyright (c) 2016 Brocade Communications Systems and others. All rights reserved. -# -# This program and the accompanying materials are made available under the -# terms of the Eclipse Public License v1.0 which accompanies this distribution, -# and is available at http://www.eclipse.org/legal/epl-v10.html -# - -''' -idmtool - -Used to manipulate ODL AAA idm on a node-per-node basis. Assumes only one domain (sdn) -since current support in ODL is limited. -''' - -__author__ = "Ryan Goulding" -__copyright__ = "Copyright (c) 2016 Brocade Communications Systems and others" -__credits__ = "Ryan Goulding" -__license__ = "EPL" -__version__ = "1.0" -__maintainer__ = "Ryan Goulding" -__email__ = "ryandgoulding@gmail.com" -__status__ = "Production" - -import argparse, getpass, json, requests, sys - -parser = argparse.ArgumentParser('idmtool') - -user='' -hostname='localhost' -protocol='http' -port='8181' -target_host='{}://{}:{}/'.format(protocol, hostname, port) - -# main program arguments -parser.add_argument('user',help='username for BSC node', nargs=1) -parser.add_argument('--target-host', help="target host node", nargs=1) - -subparsers = parser.add_subparsers(help='sub-command help') - -# users table related -list_users = subparsers.add_parser('list-users', help='list all users') -list_users.set_defaults(func=list_users) -add_user = subparsers.add_parser('add-user', help='add a user') -add_user.set_defaults(func=add_user) -add_user.add_argument('newUser', help='new user name', nargs=1) -change_password = subparsers.add_parser('change-password', help='change a password') -change_password.set_defaults(func=change_password) -change_password.add_argument('userid', help='change the password for a particular userid', nargs=1) -delete_user = subparsers.add_parser('delete-user', help='delete a user') -delete_user.add_argument('userid', help='name@sdn', nargs=1) -delete_user.set_defaults(func=delete_user) - -# domains table related -# only read is defined; this was done on purpose since the "domain" concept -# is mostly unsupported in ODL. -list_domains = subparsers.add_parser('list-domains', help='list all domains') -list_domains.set_defaults(func=list_domains) - -# roles table related -list_roles = subparsers.add_parser('list-roles', help='list all roles') -list_roles.set_defaults(func=list_roles) -add_role = subparsers.add_parser('add-role', help='add a role') -add_role.add_argument('role', help='role name', nargs=1) -add_role.set_defaults(func=add_role) -delete_role = subparsers.add_parser('delete-role', help='delete a role') -delete_role.add_argument('roleid', help='rolename@sdn', nargs=1) -delete_role.set_defaults(func=delete_role) -add_grant = subparsers.add_parser('add-grant', help='add a grant') -add_grant.set_defaults(func=add_grant) -add_grant.add_argument('userid', help="username@sdn", nargs=1) -add_grant.add_argument('roleid', help="role@sdn", nargs=1) -get_grants = subparsers.add_parser('get-grants', help='get grants for userid on sdn') -get_grants.set_defaults(func=get_grants) -get_grants.add_argument('userid', help="username@sdn", nargs=1) -delete_grant = subparsers.add_parser('delete-grant', help='delete a grant') -delete_grant.add_argument('userid', help='username@sdn', nargs=1) -delete_grant.add_argument('roleid', help='role@sdn', nargs=1) -delete_grant.set_defaults(func=delete_grant) - -def process_result(r): - ''' Generic method to print result of a REST call ''' - print '' - sc = r.status_code - if sc >= 200 and sc < 300: - print "command succeeded!" - try: - res = r.json() - if res is not None: - print '\njson:\n', json.dumps(res, indent=4, sort_keys=True) - except(ValueError): - pass - elif sc == 401: - print "Incorrect Credentials Provided" - elif sc == 404: - print "RESTconf is either not installed or not initialized yet" - elif sc >= 500 and sc < 600: - print "Internal Server Error Ocurred" - else: - print "Unknown error; HTTP status code: {}".format(sc) - -def get_request(user, password, url, description, outputResult=True): - if outputResult: - print description - try: - r = requests.get(url, auth=(user,password)) - if outputResult: - process_result(r) - return r - except(requests.exceptions.ConnectionError): - if outputResult: - print "Unable to connect; are you sure the controller is up?" - sys.exit(1) - -def post_request(user, password, url, description, payload, params): - print description - try: - r = requests.post(url, auth=(user,password), data=payload, headers=params) - process_result(r) - except(requests.exceptions.ConnectionError): - print "Unable to connect; are you sure the controller is up?" - sys.exit(1) - -def put_request(user, password, url, description, payload, params): - print description - try: - r = requests.put(url, auth=(user,password), data=payload, headers=params) - process_result(r) - except(requests.exceptions.ConnectionError): - print "Unable to connect; are you sure the controller is up?" - sys.exit(1) - -def delete_request(user, password, url, description, payload='', params={'Content-Type':'application/json'}): - print description - try: - r = requests.delete(url, auth=(user,password), data=payload, headers=params) - process_result(r) - except(requests.exceptions.ConnectionError): - print "Unable to connect; are you sure the controller is up?" - sys.exit(1) - -def poll_new_password(): - new_password = getpass.getpass(prompt="Enter new password: ") - new_password_repeated = getpass.getpass(prompt="Re-enter password: ") - if new_password != new_password_repeated: - print "Passwords did not match; cancelling the add_user request" - sys.exit(1) - return new_password - -def list_users(user, password): - get_request(user, password, target_host + 'auth/v1/users', 'list_users') - -def add_user(user, password, newUser): - new_password = poll_new_password() - description = 'add_user({})'.format(user) - url = target_host + 'auth/v1/users' - payload = {'name':newUser, 'password':new_password, 'description':'', "domainid":"sdn", 'userid':'{}@sdn'.format(newUser), 'email':''} - jsonpayload = json.dumps(payload) - headers={'Content-Type':'application/json'} - post_request(user, password, url, description, jsonpayload, headers) - -def delete_user(user, password, userid): - url = target_host + 'auth/v1/users/{}'.format(userid) - description = 'delete_user({})'.format(userid) - delete_request(user, password, url, description) - -def change_password(user, password, existingUserId): - url = target_host + 'auth/v1/users/{}'.format(existingUserId) - r = get_request(user, password, target_host + 'auth/v1/users/{}'.format(existingUserId), 'list_users', outputResult=False) - try: - existing = r.json() - del existing['salt'] - del existing['password'] - new_password = poll_new_password() - existing['password'] = new_password - description='change_password({})'.format(existingUserId) - headers={'Content-Type':'application/json'} - url = target_host + 'auth/v1/users/{}'.format(existingUserId) - put_request(user, password, url, 'change_password({})'.format(user), json.dumps(existing), headers) - except(AttributeError): - print "Unable to connect; are you sure the controller is up?" - sys.exit(1) - -def list_domains(user, password): - get_request(user, password, target_host + 'auth/v1/domains', 'list_domains') - -def list_roles(user, password): - get_request(user, password, target_host + 'auth/v1/roles', 'list_roles') - -def add_role(user, password, role): - url = target_host + 'auth/v1/roles' - description = 'add_role({})'.format(role) - payload = {"roleid":'{}@sdn'.format(role), 'name':role, 'description':'', 'domainid':'sdn'} - data = json.dumps(payload) - headers={'Content-Type':'application/json'} - post_request(user, password, url, description, data, headers) - -def delete_role(user, password, roleid): - url = target_host + 'auth/v1/roles/{}'.format(roleid) - description = 'delete_role({})'.format(roleid) - delete_request(user, password, url, description) - -def add_grant(user, password, userid, roleid): - description = 'add_grant(userid={},roleid={})'.format(userid, roleid) - payload = {"roleid":roleid, "userid":userid, "grantid":'{}@{}@{}'.format(userid, roleid, "sdn"), "domainid":"sdn"} - url = target_host + 'auth/v1/domains/sdn/users/{}/roles'.format(userid) - data=json.dumps(payload) - headers={'Content-Type':'application/json'} - post_request(user, password, url, description, data, headers) - -def get_grants(user, password, userid): - get_request(user, password, target_host + 'auth/v1/domains/sdn/users/{}/roles'.format(userid), 'get_grants({})'.format(userid)) - -def delete_grant(user, password, userid, roleid): - url = target_host + 'auth/v1/domains/sdn/users/{}/roles/{}'.format(userid, roleid) - print url - description = 'delete_grant(userid={},roleid={})'.format(userid, roleid) - delete_request(user, password, url, description) - -args = parser.parse_args() -command = args.func.prog.split()[1:] -user = args.user[0] -password = getpass.getpass() -if "list-users" in command: - list_users(user,password) -if "list-domains" in command: - list_domains(user,password) -if "list-roles" in command: - list_roles(user,password) -if "add-user" in command: - add_user(user,password, args.newUser[0]) -if "add-grant" in command: - add_grant(user,password, args.userid[0], args.roleid[0]) -if "get-grants" in command: - get_grants(user,password, args.userid[0]) -if "change-password" in command: - change_password(user, password, args.userid[0]) -if "delete-user" in command: - delete_user(user, password, args.userid[0]) -if "delete-role" in command: - delete_role(user, password, args.roleid[0]) -if "add-role" in command: - add_role(user, password, args.role[0]) -if "delete-grant" in command: - delete_grant(user, password, args.userid[0], args.roleid[0]) diff --git a/odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml b/odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml deleted file mode 100644 index 695ce762..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - authn:aaa-idmlight - aaa-idmlight - - - - - - config:aaa:authn:idmlight?module=aaa-idmlight&revision=2015-12-04 - - - - diff --git a/odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang b/odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang deleted file mode 100644 index 4f28d755..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang +++ /dev/null @@ -1,28 +0,0 @@ -module aaa-idmlight { - yang-version 1; - namespace "config:aaa:authn:idmlight"; - prefix "aaa-idmlight"; - organization "OpenDayLight"; - - import config { prefix config; revision-date 2013-04-05; } - import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; } - - contact "saichler@gmail.com"; - - revision 2015-12-04 { - description - "Initial revision."; - } - - identity aaa-idmlight { - base config:module-type; - config:java-name-prefix AAAIDMLight; - } - - augment "/config:modules/config:module/config:configuration" { - case aaa-idmlight { - when "/config:modules/config:module/config:type = 'aaa-idmlight'"; - } - } - -} diff --git a/odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java b/odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java deleted file mode 100644 index 44fadf7a..00000000 --- a/odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idm.persistence; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.opendaylight.aaa.api.IDMStoreException; -import org.opendaylight.aaa.api.IIDMStore; -import org.opendaylight.aaa.api.PasswordCredentials; -import org.opendaylight.aaa.api.SHA256Calculator; -import org.opendaylight.aaa.api.model.Domain; -import org.opendaylight.aaa.api.model.Grant; -import org.opendaylight.aaa.api.model.Grants; -import org.opendaylight.aaa.api.model.Role; -import org.opendaylight.aaa.api.model.User; -import org.opendaylight.aaa.api.model.Users; -import org.opendaylight.aaa.idm.IdmLightProxy; -import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule; - -/* - * @Author - Sharon Aicler (saichler@cisco.com) -*/ -public class PasswordHashTest { - - @Before - public void before() throws IDMStoreException{ - IIDMStore store = Mockito.mock(IIDMStore.class); - AAAIDMLightModule.setStore(store); - Domain domain = new Domain(); - domain.setName("sdn"); - domain.setDomainid("sdn"); - - Mockito.when(store.readDomain("sdn")).thenReturn(domain); - Creds c = new Creds(); - Users users = new Users(); - User user = new User(); - user.setName("admin"); - user.setUserid(c.username()); - user.setDomainid("sdn"); - user.setSalt("ABCD"); - user.setPassword(SHA256Calculator.getSHA256(c.password(),user.getSalt())); - List lu = new LinkedList<>(); - lu.add(user); - users.setUsers(lu); - - Grants grants = new Grants(); - Grant grant = new Grant(); - List g = new ArrayList<>(); - g.add(grant); - grant.setDomainid("sdn"); - grant.setRoleid("admin"); - grant.setUserid("admin"); - grants.setGrants(g); - Role role = new Role(); - role.setRoleid("admin"); - role.setName("admin"); - Mockito.when(store.readRole("admin")).thenReturn(role); - Mockito.when(store.getUsers(c.username(), c.domain())).thenReturn(users); - Mockito.when(store.getGrants(c.domain(), c.username())).thenReturn(grants); - } - - @Test - public void testPasswordHash(){ - IdmLightProxy proxy = new IdmLightProxy(); - proxy.authenticate(new Creds()); - } - - private static class Creds implements PasswordCredentials { - @Override - public String username() { - return "admin"; - } - @Override - public String password() { - return "admin"; - } - @Override - public String domain() { - return "sdn"; - } - } -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/cleardb.sh b/odl-aaa-moon/aaa-idmlight/tests/cleardb.sh deleted file mode 100644 index 6385b48d..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/cleardb.sh +++ /dev/null @@ -1,5 +0,0 @@ -sudo service idmlight stop -echo "dropping all tables..." -sleep 3 -sudo sqlite3 /opt/idmlight/dmlight.db < ../sql/idmlight.sql -sudo service idmlight start diff --git a/odl-aaa-moon/aaa-idmlight/tests/domain.json b/odl-aaa-moon/aaa-idmlight/tests/domain.json deleted file mode 100644 index 4dfd25e9..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/domain.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "domainid": "1", - "name":"R&D", - "enabled":"true" -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/domain2.json b/odl-aaa-moon/aaa-idmlight/tests/domain2.json deleted file mode 100644 index 69244b30..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/domain2.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "domainid": "1", - "name":"ATG", - "enabled":"true" -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/grant.json b/odl-aaa-moon/aaa-idmlight/tests/grant.json deleted file mode 100644 index 0c4a9e90..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/grant.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "roleid":"2", - "description":"role grant" -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/grant2.json b/odl-aaa-moon/aaa-idmlight/tests/grant2.json deleted file mode 100644 index ad685b7a..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/grant2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "roleid":"3", - "description":"role grant" -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/result.json b/odl-aaa-moon/aaa-idmlight/tests/result.json deleted file mode 100644 index a3dd995d..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/result.json +++ /dev/null @@ -1 +0,0 @@ -{"domainid":2,"userid":2,"username":"peter","roles":[{"roleid":2,"name":"user","description":"A user role with limited access"},{"roleid":3,"name":"user","description":"A user role with limited access"}]} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-idmlight/tests/role-admin.json b/odl-aaa-moon/aaa-idmlight/tests/role-admin.json deleted file mode 100644 index cf93caae..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/role-admin.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name":"admin", - "description":"An admin role with full access" -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/role-user.json b/odl-aaa-moon/aaa-idmlight/tests/role-user.json deleted file mode 100644 index 78588c9a..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/role-user.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name":"user", - "description":"A user role with limited access" -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/test.sh b/odl-aaa-moon/aaa-idmlight/tests/test.sh deleted file mode 100644 index 3589be58..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/test.sh +++ /dev/null @@ -1,308 +0,0 @@ -# GLOBAL VARS -TARGET="localhost:8282/auth" -TESTCOUNT=0 -PASSCOUNT=0 -FAILCOUNT=0 - -getit() { -((TESTCOUNT++)) -echo '['$TESTCOUNT']' $NAME -echo GET $URL -echo "Desired Result=" $PASSCODE -STATUS=$(curl -X GET -k -s -H Accept:application/json -o result.json -w '%{http_code}' $URL) -if [ $STATUS -eq $PASSCODE ]; then - ((PASSCOUNT++)) - cat result.json | python -mjson.tool - echo "[PASS] Status=" $STATUS -else - cat result.json | python -mjson.tool - echo "[FAIL] Status=" $STATUS - ((FAILCOUNT++)) -fi -echo -} - - -deleteit() { -((TESTCOUNT++)) -echo '['$TESTCOUNT']' $NAME -echo DELETE $URL -echo "Desired Result=" $PASSCODE -STATUS=$(curl -X DELETE -k -s -H Accept:application/json -o result.json -w '%{http_code}' $URL) -if [ $STATUS -eq $PASSCODE ]; then - ((PASSCOUNT++)) - echo "[PASS] Status=" $STATUS -else - cat result.json | python -mjson.tool - echo "[FAIL] Status=" $STATUS - ((FAILCOUNT++)) -fi -echo -} - -postit() { -((TESTCOUNT++)) -echo '['$TESTCOUNT']' $NAME -echo POST $URL -echo "Desired Result=" $PASSCODE -echo "POST File=" $POSTFILE -STATUS=$(curl -X POST -k -s -H "Content-type:application/json" --data-binary "@"$POSTFILE -o result.json -w '%{http_code}' $URL) -if [ $STATUS -eq $PASSCODE ]; then - ((PASSCOUNT++)) - cat result.json | python -mjson.tool - echo "[PASS] Status=" $STATUS -else - cat result.json | python -mjson.tool - echo "[FAIL] Status=" $STATUS - ((FAILCOUNT++)) -fi -echo -} - -putit() { -((TESTCOUNT++)) -echo '['$TESTCOUNT']' $NAME -echo PUT $URL -echo "Desired Result=" $PASSCODE -echo "PUT file=" $PUTFILE -STATUS=$(curl -X PUT -k -s -H "Content-type:application/json" --data-binary "@"$PUTFILE -o result.json -w '%{http_code}' $URL) -if [ $STATUS -eq $PASSCODE ]; then - ((PASSCOUNT++)) - cat result.json | python -mjson.tool - echo "[PASS] Status=" $STATUS -else - cat result.json | python -mjson.tool - echo "[FAIL] Status=" $STATUS - ((FAILCOUNT++)) -fi -echo -} - - -# -# DOMAIN TESTS -# - -NAME="get all domains" -URL="http://$TARGET/v1/domains" -PASSCODE=200 -getit - -NAME="create a new domain" -URL="http://$TARGET/v1/domains" -POSTFILE=domain.json -PASSCODE=201 -postit - -NAME="get domain 1" -URL="http://$TARGET/v1/domains/1" -PASSCODE=200 -getit - -NAME="delete domain 1" -URL="http://$TARGET/v1/domains/1" -PASSCODE=204 -deleteit - -NAME="create a new domain" -URL="http://$TARGET/v1/domains" -POSTFILE=domain.json -PASSCODE=201 -postit - -NAME="get all domains" -URL="http://$TARGET/v1/domains" -PASSCODE=200 -getit - -NAME="update domain 2" -URL="http://$TARGET/v1/domains/2" -PUTFILE=domain.json -PASSCODE=200 -putit - -NAME="create a new domain" -URL="http://$TARGET/v1/domains" -POSTFILE=domain2.json -PASSCODE=201 -postit - -NAME="get all domains" -URL="http://$TARGET/v1/domains" -PASSCODE=200 -getit - -# -# USER TESTS -# - -NAME="get all users" -URL="http://$TARGET/v1/users" -PASSCODE=200 -getit - -NAME="create a new user" -URL="http://$TARGET/v1/users" -POSTFILE=user.json -PASSCODE=201 -postit - -NAME="get all users" -URL="http://$TARGET/v1/users" -PASSCODE=200 -getit - -NAME="get user 1" -URL="http://$TARGET/v1/users/1" -PASSCODE=200 -getit - -NAME="delete user 1" -URL="http://$TARGET/v1/users/1" -PASSCODE=204 -deleteit - -NAME="get all users" -URL="http://$TARGET/v1/users" -PASSCODE=200 -getit - -NAME="create a new user" -URL="http://$TARGET/v1/users" -POSTFILE=user.json -PASSCODE=201 -postit - -NAME="update a user" -URL="http://$TARGET/v1/users/2" -PUTFILE=user.json -PASSCODE=200 -putit - -NAME="create a new user" -URL="http://$TARGET/v1/users" -POSTFILE=user2.json -PASSCODE=201 -postit - -NAME="get all users" -URL="http://$TARGET/v1/users" -PASSCODE=200 -getit - -# ROLE TESTS - -NAME="get all roles" -URL="http://$TARGET/v1/roles" -PASSCODE=200 -getit - -NAME="create a new role" -URL="http://$TARGET/v1/roles" -POSTFILE=role-user.json -PASSCODE=201 -postit - -NAME="get all roles" -URL="http://$TARGET/v1/roles" -PASSCODE=200 -getit - -NAME="get role 1" -URL="http://$TARGET/v1/roles/1" -PASSCODE=200 -getit - -NAME="delete role 1" -URL="http://$TARGET/v1/roles/1" -PASSCODE=204 -deleteit - -NAME="create a new role" -URL="http://$TARGET/v1/roles" -POSTFILE=role-user.json -PASSCODE=201 -postit - -NAME="update role 2" -URL="http://$TARGET/v1/roles/2" -PUTFILE=role-user.json -PASSCODE=200 -putit - -NAME="create a new role" -URL="http://$TARGET/v1/roles" -POSTFILE=role-admin.json -PASSCODE=201 -postit - -NAME="get all roles" -URL="http://$TARGET/v1/roles" -PASSCODE=200 -getit - -# Grant tests - -NAME="grant a role" -URL="http://$TARGET/v1/domains/2/users/2/roles" -POSTFILE=grant.json -PASSCODE=201 -postit - -NAME="try to create a double grant" -URL="http://$TARGET/v1/domains/2/users/2/roles" -POSTFILE=grant.json -PASSCODE=403 -postit - -NAME="get all roles for domain and user" -URL="http://$TARGET/v1/domains/2/users/2/roles" -PASSCODE=200 -getit - -NAME="delete a grant" -URL="http://$TARGET/v1/domains/2/users/2/roles/2" -PASSCODE=204 -deleteit - -NAME="delete a grant" -URL="http://$TARGET/v1/domains/2/users/2/roles/2" -PASSCODE=404 -deleteit - -NAME="get all roles for domain and user" -URL="http://$TARGET/v1/domains/2/users/2/roles" -PASSCODE=200 -getit - -NAME="grant a role" -URL="http://$TARGET/v1/domains/2/users/2/roles" -POSTFILE=grant.json -PASSCODE=201 -postit - -NAME="grant a role" -URL="http://$TARGET/v1/domains/2/users/2/roles" -POSTFILE=grant2.json -PASSCODE=201 -postit - -NAME="get all roles for domain and user" -URL="http://$TARGET/v1/domains/2/users/2/roles" -PASSCODE=200 -getit - -NAME="get all roles for domain, user and pwd" -URL="http://$TARGET/v1/domains/2/users/roles" -POSTFILE=userpwd.json -PASSCODE=200 -postit - - -# -# RESULTS -# -echo "SUMMARY" -echo "======================================" -echo 'TESTS:'$TESTCOUNT 'PASS:'$PASSCOUNT 'FAIL:'$FAILCOUNT - diff --git a/odl-aaa-moon/aaa-idmlight/tests/user.json b/odl-aaa-moon/aaa-idmlight/tests/user.json deleted file mode 100644 index 6f30d705..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/user.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name":"peter", - "description":"peter test user", - "enabled":"true", - "email":"user1@gmail.com", - "password":"foobar" -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/user2.json b/odl-aaa-moon/aaa-idmlight/tests/user2.json deleted file mode 100644 index 9864cdb2..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/user2.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name":"liem", - "description":"liem test user", - "enabled":"true", - "email":"user1@gmail.com", - "password":"foobar" -} diff --git a/odl-aaa-moon/aaa-idmlight/tests/userpwd.json b/odl-aaa-moon/aaa-idmlight/tests/userpwd.json deleted file mode 100644 index e5258b98..00000000 --- a/odl-aaa-moon/aaa-idmlight/tests/userpwd.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "username":"peter", - "userpwd":"foobar" -} diff --git a/odl-aaa-moon/aaa-idp-mapping/pom.xml b/odl-aaa-moon/aaa-idp-mapping/pom.xml deleted file mode 100644 index 99c2322d..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/pom.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-authn-idpmapping - 0.3.1-Beryllium-SR1 - bundle - - - 1.5.2 - - - - - org.glassfish - javax.json - - - org.osgi - org.osgi.core - provided - - - org.slf4j - slf4j-api - - - org.apache.felix - org.apache.felix.dependencymanager - provided - - - - - junit - junit - test - - - org.mockito - mockito-all - test - - - org.slf4j - slf4j-simple - test - - - org.powermock - powermock-api-mockito - ${powermock.version} - test - - - org.powermock - powermock-module-junit4 - ${powermock.version} - test - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.opendaylight.aaa.idpmapping.Activator - - ${project.basedir}/META-INF - - - - - diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Activator.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Activator.java deleted file mode 100644 index 7342485e..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Activator.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.osgi.framework.BundleContext; - -public class Activator extends DependencyActivatorBase { - - @Override - public void init(BundleContext context, DependencyManager manager) throws Exception { - } - - @Override - public void destroy(BundleContext context, DependencyManager manager) throws Exception { - } - -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/IdpJson.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/IdpJson.java deleted file mode 100644 index 00328b60..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/IdpJson.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import javax.json.Json; -import javax.json.JsonValue; -import javax.json.stream.JsonGenerator; -import javax.json.stream.JsonGeneratorFactory; -import javax.json.stream.JsonLocation; -import javax.json.stream.JsonParser; -import javax.json.stream.JsonParser.Event; - -/** - * Converts between JSON and the internal data structures used in the - * RuleProcessor. - * - * @author John Dennis <jdennis@redhat.com> - */ - -public class IdpJson { - - public IdpJson() { - } - - public Object loadJson(java.io.Reader in) { - JsonParser parser = Json.createParser(in); - Event event = null; - - // Prime the pump. Get the first item from the parser. - event = parser.next(); - - // Act on first item. - return loadJsonItem(parser, event); - } - - public Object loadJson(Path filename) throws IOException { - BufferedReader reader = Files.newBufferedReader(filename, StandardCharsets.UTF_8); - return loadJson(reader); - } - - public Object loadJson(String string) { - StringReader reader = new StringReader(string); - return loadJson(reader); - } - - /* - * Process current parser item indicated by event. Consumes exactly the - * number of parser events necessary to load the item. Caller must advance - * the parser via parser.next() after this method returns. - */ - private Object loadJsonItem(JsonParser parser, Event event) { - switch (event) { - case START_OBJECT: { - return loadJsonObject(parser, event); - } - case START_ARRAY: { - return loadJsonArray(parser, event); - } - case VALUE_NULL: { - return null; - } - case VALUE_NUMBER: { - if (parser.isIntegralNumber()) { - return parser.getLong(); - } else { - return parser.getBigDecimal().doubleValue(); - } - } - case VALUE_STRING: { - return parser.getString(); - } - case VALUE_TRUE: { - return Boolean.TRUE; - } - case VALUE_FALSE: { - return Boolean.FALSE; - } - default: { - JsonLocation location = parser.getLocation(); - throw new IllegalStateException(String.format( - "unknown JSON parsing event %s, location(line=%d column=%d offset=%d)", event, - location.getLineNumber(), location.getColumnNumber(), - location.getStreamOffset())); - } - } - } - - private List loadJsonArray(JsonParser parser, Event event) { - List list = new ArrayList(); - - if (event != Event.START_ARRAY) { - JsonLocation location = parser.getLocation(); - throw new IllegalStateException( - String.format( - "expected JSON parsing event to be START_ARRAY, not %s location(line=%d column=%d offset=%d)", - event, location.getLineNumber(), location.getColumnNumber(), - location.getStreamOffset())); - } - event = parser.next(); // consume START_ARRAY - while (event != Event.END_ARRAY) { - Object obj; - - obj = loadJsonItem(parser, event); - list.add(obj); - event = parser.next(); // next array item or END_ARRAY - } - return list; - } - - private Map loadJsonObject(JsonParser parser, Event event) { - Map map = new LinkedHashMap(); - - if (event != Event.START_OBJECT) { - JsonLocation location = parser.getLocation(); - throw new IllegalStateException(String.format( - "expected JSON parsing event to be START_OBJECT, not %s, ", - "location(line=%d column=%d offset=%d)", event, location.getLineNumber(), - location.getColumnNumber(), location.getStreamOffset())); - } - event = parser.next(); // consume START_OBJECT - while (event != Event.END_OBJECT) { - if (event == Event.KEY_NAME) { - String key; - Object value; - - key = parser.getString(); - event = parser.next(); // consume key - value = loadJsonItem(parser, event); - map.put(key, value); - } else { - JsonLocation location = parser.getLocation(); - throw new IllegalStateException( - String.format( - "expected JSON parsing event to be KEY_NAME, not %s, location(line=%d column=%d offset=%d)", - event, location.getLineNumber(), location.getColumnNumber(), - location.getStreamOffset())); - - } - event = parser.next(); // next key or END_OBJECT - } - return map; - } - - public String dumpJson(Object obj) { - Map properties = new HashMap(1); - properties.put(JsonGenerator.PRETTY_PRINTING, true); - JsonGeneratorFactory generatorFactory = Json.createGeneratorFactory(properties); - StringWriter stringWriter = new StringWriter(); - JsonGenerator generator = generatorFactory.createGenerator(stringWriter); - - dumpJsonItem(generator, obj); - generator.close(); - return stringWriter.toString(); - } - - private void dumpJsonItem(JsonGenerator generator, Object obj) { - // ordered by expected occurrence - if (obj instanceof String) { - generator.write((String) obj); - } else if (obj instanceof List) { - generator.writeStartArray(); - @SuppressWarnings("unchecked") - List list = (List) obj; - dumpJsonArray(generator, list); - } else if (obj instanceof Map) { - generator.writeStartObject(); - @SuppressWarnings("unchecked") - Map map = (Map) obj; - dumpJsonObject(generator, map); - } else if (obj instanceof Long) { - generator.write(((Long) obj).longValue()); - } else if (obj instanceof Boolean) { - generator.write(((Boolean) obj).booleanValue()); - } else if (obj == null) { - generator.writeNull(); - } else if (obj instanceof Double) { - generator.write(((Double) obj).doubleValue()); - } else { - throw new IllegalStateException( - String.format( - "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s", - obj.getClass().getSimpleName())); - } - } - - private void dumpJsonArray(JsonGenerator generator, List list) { - for (Object obj : list) { - dumpJsonItem(generator, obj); - } - generator.writeEnd(); - } - - private void dumpJsonObject(JsonGenerator generator, Map map) { - - for (Map.Entry entry : map.entrySet()) { - String key = entry.getKey(); - Object obj = entry.getValue(); - - // ordered by expected occurrence - if (obj instanceof String) { - generator.write(key, (String) obj); - } else if (obj instanceof List) { - generator.writeStartArray(key); - @SuppressWarnings("unchecked") - List list = (List) obj; - dumpJsonArray(generator, list); - } else if (obj instanceof Map) { - generator.writeStartObject(key); - @SuppressWarnings("unchecked") - Map map1 = (Map) obj; - dumpJsonObject(generator, map1); - } else if (obj instanceof Long) { - generator.write(key, ((Long) obj).longValue()); - } else if (obj instanceof Boolean) { - generator.write(key, ((Boolean) obj).booleanValue()); - } else if (obj == null) { - generator.write(key, JsonValue.NULL); - } else if (obj instanceof Double) { - generator.write(key, ((Double) obj).doubleValue()); - } else { - throw new IllegalStateException( - String.format( - "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s", - obj.getClass().getSimpleName())); - } - } - generator.writeEnd(); - } - -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidRuleException.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidRuleException.java deleted file mode 100644 index 1e42f4f2..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidRuleException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -/** - * Exception thrown when a mapping rule is improperly defined. - * - * @author John Dennis <jdennis@redhat.com> - */ - -public class InvalidRuleException extends RuntimeException { - - private static final long serialVersionUID = 1948891573270429630L; - - public InvalidRuleException() { - } - - public InvalidRuleException(String message) { - super(message); - } - - public InvalidRuleException(Throwable cause) { - super(cause); - } - - public InvalidRuleException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidTypeException.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidTypeException.java deleted file mode 100644 index fb8b132f..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidTypeException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -/** - * Exception thrown when the type of a value is incorrect for a given context. - * - * @author John Dennis <jdennis@redhat.com> - */ - -public class InvalidTypeException extends RuntimeException { - - private static final long serialVersionUID = 4437011247503994368L; - - public InvalidTypeException() { - } - - public InvalidTypeException(String message) { - super(message); - } - - public InvalidTypeException(Throwable cause) { - super(cause); - } - - public InvalidTypeException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidValueException.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidValueException.java deleted file mode 100644 index 2f83c13f..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidValueException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -/** - * Exception thrown when a value cannot be used in a given context. - * - * @author John Dennis <jdennis@redhat.com> - */ - -public class InvalidValueException extends RuntimeException { - - private static final long serialVersionUID = -2351651535772692180L; - - public InvalidValueException() { - } - - public InvalidValueException(String message) { - super(message); - } - - public InvalidValueException(Throwable cause) { - super(cause); - } - - public InvalidValueException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java deleted file mode 100644 index 0f86fde6..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java +++ /dev/null @@ -1,1368 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -import java.io.IOException; -import java.io.StringWriter; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -enum ProcessResult { - RULE_FAIL, RULE_SUCCESS, BLOCK_CONTINUE, STATEMENT_CONTINUE -} - -/** - * Evaluate a set of rules against an assertion from an external Identity - * Provider (IdP) mapping those assertion values to local values. - * - * @author John Dennis <jdennis@redhat.com> - */ - -public class RuleProcessor { - private static final Logger LOG = LoggerFactory.getLogger(RuleProcessor.class); - - public String ruleIdFormat = ""; - public String statementIdFormat = ""; - - /* - * Reserved variables - */ - public static final String ASSERTION = "assertion"; - public static final String RULE_NUMBER = "rule_number"; - public static final String RULE_NAME = "rule_name"; - public static final String BLOCK_NUMBER = "block_number"; - public static final String BLOCK_NAME = "block_name"; - public static final String STATEMENT_NUMBER = "statement_number"; - public static final String REGEXP_ARRAY_VARIABLE = "regexp_array"; - public static final String REGEXP_MAP_VARIABLE = "regexp_map"; - - private static final String REGEXP_NAMED_GROUP_PAT = "\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>"; - private static final Pattern REGEXP_NAMED_GROUP_RE = Pattern.compile(REGEXP_NAMED_GROUP_PAT); - - List> rules = null; - boolean success = true; - Map> mappings = null; - - public RuleProcessor(java.io.Reader rulesIn, Map> mappings) { - this.mappings = mappings; - IdpJson json = new IdpJson(); - @SuppressWarnings("unchecked") - List> loadJson = (List>) json.loadJson(rulesIn); - rules = loadJson; - } - - public RuleProcessor(Path rulesIn, Map> mappings) - throws IOException { - this.mappings = mappings; - IdpJson json = new IdpJson(); - @SuppressWarnings("unchecked") - List> loadJson = (List>) json.loadJson(rulesIn); - rules = loadJson; - } - - public RuleProcessor(String rulesIn, Map> mappings) { - this.mappings = mappings; - IdpJson json = new IdpJson(); - @SuppressWarnings("unchecked") - List> loadJson = (List>) json.loadJson(rulesIn); - rules = loadJson; - } - - /* - * For some odd reason the Java Regular Expression API does not include a - * way to retrieve a map of the named groups and their values. The API only - * permits us to retrieve a named group if we already know the group names. - * So instead we parse the pattern string looking for named groups, extract - * the name, look up the value of the named group and build a map from that. - */ - - private Map regexpGroupMap(String pattern, Matcher matcher) { - Map groupMap = new HashMap(); - Matcher groupMatcher = REGEXP_NAMED_GROUP_RE.matcher(pattern); - - while (groupMatcher.find()) { - String groupName = groupMatcher.group(1); - - groupMap.put(groupName, matcher.group(groupName)); - } - return groupMap; - } - - static public String join(List list, String conjunction) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (Object item : list) { - if (first) { - first = false; - } else { - sb.append(conjunction); - } - sb.append(item.toString()); - } - return sb.toString(); - } - - private List regexpGroupList(Matcher matcher) { - List groupList = new ArrayList(matcher.groupCount() + 1); - groupList.add(0, matcher.group(0)); - for (int i = 1; i < matcher.groupCount() + 1; i++) { - groupList.add(i, matcher.group(i)); - } - return groupList; - } - - private String objToString(Object obj) { - StringWriter sw = new StringWriter(); - objToStringItem(sw, obj); - return sw.toString(); - } - - private void objToStringItem(StringWriter sw, Object obj) { - // ordered by expected occurrence - if (obj instanceof String) { - sw.write('"'); - sw.write(((String) obj).replaceAll("\"", "\\\"")); - sw.write('"'); - } else if (obj instanceof List) { - @SuppressWarnings("unchecked") - List list = (List) obj; - boolean first = true; - - sw.write('['); - for (Object item : list) { - if (first) { - first = false; - } else { - sw.write(", "); - } - objToStringItem(sw, item); - } - sw.write(']'); - } else if (obj instanceof Map) { - @SuppressWarnings("unchecked") - Map map = (Map) obj; - boolean first = true; - - sw.write('{'); - for (Map.Entry entry : map.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - - if (first) { - first = false; - } else { - sw.write(", "); - } - - objToStringItem(sw, key); - sw.write(": "); - objToStringItem(sw, value); - - } - sw.write('}'); - } else if (obj instanceof Long) { - sw.write(((Long) obj).toString()); - } else if (obj instanceof Boolean) { - sw.write(((Boolean) obj).toString()); - } else if (obj == null) { - sw.write("null"); - } else if (obj instanceof Double) { - sw.write(((Double) obj).toString()); - } else { - throw new IllegalStateException( - String.format( - "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s", - obj.getClass().getSimpleName())); - } - } - - private Object deepCopy(Object obj) { - // ordered by expected occurrence - if (obj instanceof String) { - return obj; // immutable - } else if (obj instanceof List) { - List new_list = new ArrayList(); - @SuppressWarnings("unchecked") - List list = (List) obj; - for (Object item : list) { - new_list.add(deepCopy(item)); - } - return new_list; - } else if (obj instanceof Map) { - Map new_map = new LinkedHashMap(); - @SuppressWarnings("unchecked") - Map map = (Map) obj; - for (Map.Entry entry : map.entrySet()) { - String key = entry.getKey(); // immutable - Object value = entry.getValue(); - new_map.put(key, deepCopy(value)); - } - return new_map; - } else if (obj instanceof Long) { - return obj; // immutable - } else if (obj instanceof Boolean) { - return obj; // immutable - } else if (obj == null) { - return null; - } else if (obj instanceof Double) { - return obj; // immutable - } else { - throw new IllegalStateException( - String.format( - "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s", - obj.getClass().getSimpleName())); - } - } - - public String ruleId(Map namespace) { - return substituteVariables(ruleIdFormat, namespace); - } - - public String statementId(Map namespace) { - return substituteVariables(statementIdFormat, namespace); - } - - public String substituteVariables(String string, Map namespace) { - StringBuffer sb = new StringBuffer(); - Matcher matcher = Token.VARIABLE_RE.matcher(string); - - while (matcher.find()) { - Token token = new Token(matcher.group(0), namespace); - token.load(); - String replacement; - if (token.type == TokenType.STRING) { - replacement = token.getStringValue(); - } else { - replacement = objToString(token.getObjectValue()); - } - - matcher.appendReplacement(sb, replacement); - } - matcher.appendTail(sb); - return sb.toString(); - } - - Map getMapping(Map namespace, Map rule) { - Map mapping = null; - String mappingName = null; - - try { - @SuppressWarnings("unchecked") - Map map = (Map) rule.get("mapping"); - mapping = map; - } catch (java.lang.ClassCastException e) { - throw new InvalidRuleException(String.format( - "%s rule defines 'mapping' but it is not a Map", this.ruleId(namespace), e)); - } - if (mapping != null) { - return mapping; - } - try { - mappingName = (String) rule.get("mapping_name"); - } catch (java.lang.ClassCastException e) { - throw new InvalidRuleException(String.format( - "%s rule defines 'mapping_name' but it is not a string", - this.ruleId(namespace), e)); - } - if (mappingName == null) { - throw new InvalidRuleException(String.format( - "%s rule does not define mapping nor mapping_name unable to load mapping", - this.ruleId(namespace))); - } - mapping = this.mappings.get(mappingName); - if (mapping == null) { - throw new InvalidRuleException( - String.format( - "%s rule specifies mapping_name '%s' but a mapping by that name does not exist, unable to load mapping", - this.ruleId(namespace))); - } - LOG.debug(String.format("using named mapping '%s' from rule %s mapping=%s", mappingName, - this.ruleId(namespace), mapping)); - return mapping; - } - - private String getVerb(List statement) { - Token verb; - - if (statement.size() < 1) { - throw new InvalidRuleException("statement has no verb"); - } - - try { - verb = new Token(statement.get(0), null); - } catch (Exception e) { - throw new InvalidRuleException(String.format( - "statement first member (i.e. verb) error %s", e)); - } - - if (verb.type != TokenType.STRING) { - throw new InvalidRuleException(String.format( - "statement first member (i.e. verb) must be a string, not %s", verb.type)); - } - - return (verb.getStringValue()).toLowerCase(); - } - - private Token getToken(String verb, List statement, int index, - Map namespace, Set storageTypes, - Set tokenTypes) { - Object item; - Token token; - - try { - item = statement.get(index); - } catch (IndexOutOfBoundsException e) { - throw new InvalidRuleException(String.format( - "verb '%s' requires at least %d items but only %d are available.", verb, - index + 1, statement.size(), e)); - } - - try { - token = new Token(item, namespace); - } catch (Exception e) { - throw new StatementErrorException(String.format("parameter %d, %s", index, e)); - } - - if (storageTypes != null) { - if (!storageTypes.contains(token.storageType)) { - throw new InvalidTypeException( - String.format( - "verb '%s' requires parameter #%d to have storage types %s not %s. statement=%s", - verb, index, storageTypes, statement)); - } - } - - if (tokenTypes != null) { - token.load(); // Note, Token.load() sets the Token.type - - if (!tokenTypes.contains(token.type)) { - throw new InvalidTypeException(String.format( - "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s", - verb, index, tokenTypes, statement)); - } - } - - return token; - } - - private Token getParameter(String verb, List statement, int index, - Map namespace, Set tokenTypes) { - Object item; - Token token; - - try { - item = statement.get(index); - } catch (IndexOutOfBoundsException e) { - throw new InvalidRuleException(String.format( - "verb '%s' requires at least %d items but only %d are available.", verb, - index + 1, statement.size(), e)); - } - - try { - token = new Token(item, namespace); - } catch (Exception e) { - throw new StatementErrorException(String.format("parameter %d, %s", index, e)); - } - - token.load(); - - if (tokenTypes != null) { - try { - token.get(); // Note, Token.get() sets the Token.type - } catch (UndefinedValueException e) { - // OK if not yet defined - } - if (!tokenTypes.contains(token.type)) { - throw new InvalidTypeException(String.format( - "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s", - verb, index, tokenTypes, item.getClass().getSimpleName(), statement)); - } - } - - return token; - } - - private Object getRawParameter(String verb, List statement, int index, - Set tokenTypes) { - Object item; - - try { - item = statement.get(index); - } catch (IndexOutOfBoundsException e) { - throw new InvalidRuleException(String.format( - "verb '%s' requires at least %d items but only %d are available.", verb, - index + 1, statement.size(), e)); - } - - if (tokenTypes != null) { - TokenType itemType = Token.classify(item); - - if (!tokenTypes.contains(itemType)) { - throw new InvalidTypeException(String.format( - "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s", - verb, index, tokenTypes, statement)); - } - } - - return item; - } - - private Token getVariable(String verb, List statement, int index, - Map namespace) { - Object item; - Token token; - - try { - item = statement.get(index); - } catch (IndexOutOfBoundsException e) { - throw new InvalidRuleException(String.format( - "verb '%s' requires at least %d items but only %d are available.", verb, - index + 1, statement.size(), e)); - } - - try { - token = new Token(item, namespace); - } catch (Exception e) { - throw new StatementErrorException(String.format("parameter %d, %s", index, e)); - } - - if (token.storageType != TokenStorageType.VARIABLE) { - throw new InvalidTypeException(String.format( - "verb '%s' requires parameter #%d to be a variable not %s. statement=%s", verb, - index, token.storageType, statement)); - } - - return token; - } - - public Map process(String assertionJson) { - ProcessResult result; - IdpJson json = new IdpJson(); - @SuppressWarnings("unchecked") - Map assertion = (Map) json.loadJson(assertionJson); - LOG.info("Assertion JSON: {}", json.dumpJson(assertion)); - this.success = true; - - for (int ruleNumber = 0; ruleNumber < this.rules.size(); ruleNumber++) { - Map namespace = new HashMap(); - Map rule = (Map) this.rules.get(ruleNumber); - namespace.put(RULE_NUMBER, Long.valueOf(ruleNumber)); - namespace.put(RULE_NAME, ""); - namespace.put(ASSERTION, deepCopy(assertion)); - - result = processRule(namespace, rule); - - if (result == ProcessResult.RULE_SUCCESS) { - Map mapped = new LinkedHashMap(); - Map mapping = getMapping(namespace, rule); - for (Map.Entry entry : ((Map) mapping).entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - Object newValue = null; - try { - Token token = new Token(value, namespace); - newValue = token.get(); - } catch (Exception e) { - throw new InvalidRuleException(String.format( - "%s unable to get value for mapping %s=%s, %s", ruleId(namespace), - key, value, e), e); - } - mapped.put(key, newValue); - } - return mapped; - } - } - return null; - } - - private ProcessResult processRule(Map namespace, Map rule) { - ProcessResult result = ProcessResult.BLOCK_CONTINUE; - @SuppressWarnings("unchecked") - List>> statementBlocks = (List>>) rule.get("statement_blocks"); - if (statementBlocks == null) { - throw new InvalidRuleException("rule missing 'statement_blocks'"); - - } - for (int blockNumber = 0; blockNumber < statementBlocks.size(); blockNumber++) { - List> block = (List>) statementBlocks.get(blockNumber); - namespace.put(BLOCK_NUMBER, Long.valueOf(blockNumber)); - namespace.put(BLOCK_NAME, ""); - - result = processBlock(namespace, block); - if (EnumSet.of(ProcessResult.RULE_SUCCESS, ProcessResult.RULE_FAIL).contains(result)) { - break; - } else if (result == ProcessResult.BLOCK_CONTINUE) { - continue; - } else { - throw new IllegalStateException(String.format("%s unexpected statement result: %s", - result)); - } - } - if (EnumSet.of(ProcessResult.RULE_SUCCESS, ProcessResult.BLOCK_CONTINUE).contains(result)) { - return ProcessResult.RULE_SUCCESS; - } else { - return ProcessResult.RULE_FAIL; - } - } - - private ProcessResult processBlock(Map namespace, List> block) { - ProcessResult result = ProcessResult.STATEMENT_CONTINUE; - - for (int statementNumber = 0; statementNumber < block.size(); statementNumber++) { - List statement = (List) block.get(statementNumber); - namespace.put(STATEMENT_NUMBER, Long.valueOf(statementNumber)); - - try { - result = processStatement(namespace, statement); - } catch (Exception e) { - throw new IllegalStateException(String.format("%s statement=%s %s", - statementId(namespace), statement, e), e); - } - if (EnumSet.of(ProcessResult.BLOCK_CONTINUE, ProcessResult.RULE_SUCCESS, - ProcessResult.RULE_FAIL).contains(result)) { - break; - } else if (result == ProcessResult.STATEMENT_CONTINUE) { - continue; - } else { - throw new IllegalStateException(String.format("%s unexpected statement result: %s", - result)); - } - } - if (result == ProcessResult.STATEMENT_CONTINUE) { - result = ProcessResult.BLOCK_CONTINUE; - } - return result; - } - - private ProcessResult processStatement(Map namespace, List statement) { - ProcessResult result = ProcessResult.STATEMENT_CONTINUE; - String verb = getVerb(statement); - - switch (verb) { - case "set": - result = verbSet(verb, namespace, statement); - break; - case "length": - result = verbLength(verb, namespace, statement); - break; - case "interpolate": - result = verbInterpolate(verb, namespace, statement); - break; - case "append": - result = verbAppend(verb, namespace, statement); - break; - case "unique": - result = verbUnique(verb, namespace, statement); - break; - case "split": - result = verbSplit(verb, namespace, statement); - break; - case "join": - result = verbJoin(verb, namespace, statement); - break; - case "lower": - result = verbLower(verb, namespace, statement); - break; - case "upper": - result = verbUpper(verb, namespace, statement); - break; - case "in": - result = verbIn(verb, namespace, statement); - break; - case "not_in": - result = verbNotIn(verb, namespace, statement); - break; - case "compare": - result = verbCompare(verb, namespace, statement); - break; - case "regexp": - result = verbRegexp(verb, namespace, statement); - break; - case "regexp_replace": - result = verbRegexpReplace(verb, namespace, statement); - break; - case "exit": - result = verbExit(verb, namespace, statement); - break; - case "continue": - result = verbContinue(verb, namespace, statement); - break; - default: - throw new InvalidRuleException(String.format("unknown verb '%s'", verb)); - } - - return result; - } - - private ProcessResult verbSet(String verb, Map namespace, List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - Token parameter = getParameter(verb, statement, 2, namespace, null); - - variable.set(parameter.getObjectValue()); - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s", - statementId(namespace), verb, this.success, variable, variable.get())); - } - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbLength(String verb, Map namespace, - List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - Token parameter = getParameter(verb, statement, 2, namespace, - EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING)); - long length; - - switch (parameter.type) { - case ARRAY: { - length = parameter.getListValue().size(); - } - break; - case MAP: { - length = parameter.getMapValue().size(); - } - break; - case STRING: { - length = parameter.getStringValue().length(); - } - break; - default: - throw new IllegalStateException(String.format("unexpected token type: %s", - parameter.type)); - } - - variable.set(length); - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s", - statementId(namespace), verb, this.success, variable, variable.get(), - parameter.getObjectValue())); - } - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbInterpolate(String verb, Map namespace, - List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - String string = (String) getRawParameter(verb, statement, 2, EnumSet.of(TokenType.STRING)); - String newValue = null; - - try { - newValue = substituteVariables(string, namespace); - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, variable='%s' string='%s': %s", verb, variable, string, e)); - } - variable.set(newValue); - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s string='%s'", - statementId(namespace), verb, this.success, variable, variable.get(), string)); - } - - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbAppend(String verb, Map namespace, - List statement) { - Token variable = getToken(verb, statement, 1, namespace, - EnumSet.of(TokenStorageType.VARIABLE), EnumSet.of(TokenType.ARRAY)); - Token item = getParameter(verb, statement, 2, namespace, null); - - try { - List list = variable.getListValue(); - list.add(item.getObjectValue()); - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, variable='%s' item='%s': %s", verb, - variable.getObjectValue(), item.getObjectValue(), e)); - } - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s item=%s", - statementId(namespace), verb, this.success, variable, variable.get(), - item.getObjectValue())); - } - - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbUnique(String verb, Map namespace, - List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - Token array = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY)); - - List newValue = new ArrayList(); - Set seen = new HashSet(); - - for (Object member : array.getListValue()) { - if (seen.contains(member)) { - continue; - } else { - newValue.add(member); - seen.add(member); - } - } - - variable.set(newValue); - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s array=%s", - statementId(namespace), verb, this.success, variable, variable.get(), - array.getObjectValue())); - } - - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbSplit(String verb, Map namespace, - List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - Token string = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); - Token pattern = getParameter(verb, statement, 3, namespace, EnumSet.of(TokenType.STRING)); - - Pattern regexp; - List newValue; - - try { - regexp = Pattern.compile(pattern.getStringValue()); - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, bad regular expression pattern '%s', %s", verb, - pattern.getObjectValue(), e)); - } - try { - newValue = new ArrayList( - Arrays.asList(regexp.split((String) string.getStringValue()))); - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, string='%s' pattern='%s', %s", verb, - string.getObjectValue(), pattern.getObjectValue(), e)); - } - - variable.set(newValue); - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format( - "%s verb='%s' success=%s variable: %s=%s string='%s' pattern='%s'", - statementId(namespace), verb, this.success, variable, variable.get(), - string.getObjectValue(), pattern.getObjectValue())); - } - - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbJoin(String verb, Map namespace, - List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - Token array = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY)); - Token conjunction = getParameter(verb, statement, 3, namespace, - EnumSet.of(TokenType.STRING)); - String newValue; - - try { - newValue = join(array.getListValue(), conjunction.getStringValue()); - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, array=%s conjunction='%s', %s", verb, - array.getObjectValue(), conjunction.getObjectValue(), e)); - } - - variable.set(newValue); - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format( - "%s verb='%s' success=%s variable: %s=%s array='%s' conjunction='%s'", - statementId(namespace), verb, this.success, variable, variable.get(), - array.getObjectValue(), conjunction.getObjectValue())); - } - - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbLower(String verb, Map namespace, - List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - Token parameter = getParameter(verb, statement, 2, namespace, - EnumSet.of(TokenType.STRING, TokenType.ARRAY, TokenType.MAP)); - - try { - switch (parameter.type) { - case STRING: { - String oldValue = parameter.getStringValue(); - String newValue; - newValue = oldValue.toLowerCase(); - variable.set(newValue); - } - break; - case ARRAY: { - List oldValue = parameter.getListValue(); - List newValue = new ArrayList(oldValue.size()); - String oldItem; - String newItem; - - for (Object item : oldValue) { - try { - oldItem = (String) item; - } catch (ClassCastException e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, array item (%s) is not a string, array=%s", - verb, item, parameter.getObjectValue(), e)); - } - newItem = oldItem.toLowerCase(); - newValue.add(newItem); - } - variable.set(newValue); - } - break; - case MAP: { - Map oldValue = parameter.getMapValue(); - Map newValue = new LinkedHashMap(oldValue.size()); - - for (Map.Entry entry : oldValue.entrySet()) { - String oldKey; - String newKey; - Object value = entry.getValue(); - - oldKey = entry.getKey(); - newKey = oldKey.toLowerCase(); - newValue.put(newKey, value); - } - variable.set(newValue); - } - break; - default: - throw new IllegalStateException(String.format("unexpected token type: %s", - parameter.type)); - } - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, variable='%s' parameter='%s': %s", verb, variable, - parameter.getObjectValue(), e), e); - } - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s", - statementId(namespace), verb, this.success, variable, variable.get(), - parameter.getObjectValue())); - } - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbUpper(String verb, Map namespace, - List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - Token parameter = getParameter(verb, statement, 2, namespace, - EnumSet.of(TokenType.STRING, TokenType.ARRAY, TokenType.MAP)); - - try { - switch (parameter.type) { - case STRING: { - String oldValue = parameter.getStringValue(); - String newValue; - newValue = oldValue.toUpperCase(); - variable.set(newValue); - } - break; - case ARRAY: { - List oldValue = parameter.getListValue(); - List newValue = new ArrayList(oldValue.size()); - String oldItem; - String newItem; - - for (Object item : oldValue) { - try { - oldItem = (String) item; - } catch (ClassCastException e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, array item (%s) is not a string, array=%s", - verb, item, parameter.getObjectValue(), e)); - } - newItem = oldItem.toUpperCase(); - newValue.add(newItem); - } - variable.set(newValue); - } - break; - case MAP: { - Map oldValue = parameter.getMapValue(); - Map newValue = new LinkedHashMap(oldValue.size()); - - for (Map.Entry entry : oldValue.entrySet()) { - String oldKey; - String newKey; - Object value = entry.getValue(); - - oldKey = entry.getKey(); - newKey = oldKey.toUpperCase(); - newValue.put(newKey, value); - } - variable.set(newValue); - } - break; - default: - throw new IllegalStateException(String.format("unexpected token type: %s", - parameter.type)); - } - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, variable='%s' parameter='%s': %s", verb, variable, - parameter.getObjectValue(), e), e); - } - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s", - statementId(namespace), verb, this.success, variable, variable.get(), - parameter.getObjectValue())); - } - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbIn(String verb, Map namespace, List statement) { - Token member = getParameter(verb, statement, 1, namespace, null); - Token collection = getParameter(verb, statement, 2, namespace, - EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING)); - - switch (collection.type) { - case ARRAY: { - this.success = collection.getListValue().contains(member.getObjectValue()); - } - break; - case MAP: { - if (member.type != TokenType.STRING) { - throw new InvalidTypeException(String.format( - "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s", - TokenType.STRING, collection.type)); - } - this.success = collection.getMapValue().containsKey(member.getObjectValue()); - } - break; - case STRING: { - if (member.type != TokenType.STRING) { - throw new InvalidTypeException(String.format( - "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s", - TokenType.STRING, collection.type)); - } - this.success = (collection.getStringValue()).contains(member.getStringValue()); - } - break; - default: - throw new IllegalStateException(String.format("unexpected token type: %s", - collection.type)); - } - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s member=%s collection=%s", - statementId(namespace), verb, this.success, member.getObjectValue(), - collection.getObjectValue())); - } - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbNotIn(String verb, Map namespace, - List statement) { - Token member = getParameter(verb, statement, 1, namespace, null); - Token collection = getParameter(verb, statement, 2, namespace, - EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING)); - - switch (collection.type) { - case ARRAY: { - this.success = !collection.getListValue().contains(member.getObjectValue()); - } - break; - case MAP: { - if (member.type != TokenType.STRING) { - throw new InvalidTypeException(String.format( - "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s", - TokenType.STRING, collection.type)); - } - this.success = !collection.getMapValue().containsKey(member.getObjectValue()); - } - break; - case STRING: { - if (member.type != TokenType.STRING) { - throw new InvalidTypeException(String.format( - "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s", - TokenType.STRING, collection.type)); - } - this.success = !(collection.getStringValue()).contains(member.getStringValue()); - } - break; - default: - throw new IllegalStateException(String.format("unexpected token type: %s", - collection.type)); - } - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s member=%s collection=%s", - statementId(namespace), verb, this.success, member.getObjectValue(), - collection.getObjectValue())); - } - - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbCompare(String verb, Map namespace, - List statement) { - Token left = getParameter(verb, statement, 1, namespace, null); - Token op = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); - Token right = getParameter(verb, statement, 3, namespace, null); - String invalidOp = "operator %s not supported for type %s"; - TokenType tokenType; - String opValue = op.getStringValue(); - boolean result; - - if (left.type != right.type) { - throw new InvalidTypeException(String.format( - "verb '%s' both items must have the same type left is %s and right is %s", - verb, left.type, right.type)); - } else { - tokenType = left.type; - } - - switch (opValue) { - case "==": - case "!=": { - switch (tokenType) { - case STRING: { - String leftValue = left.getStringValue(); - String rightValue = right.getStringValue(); - result = leftValue.equals(rightValue); - } - break; - case INTEGER: { - Long leftValue = left.getLongValue(); - Long rightValue = right.getLongValue(); - result = leftValue.equals(rightValue); - } - break; - case REAL: { - Double leftValue = left.getDoubleValue(); - Double rightValue = right.getDoubleValue(); - result = leftValue.equals(rightValue); - } - break; - case ARRAY: { - List leftValue = left.getListValue(); - List rightValue = right.getListValue(); - result = leftValue.equals(rightValue); - } - break; - case MAP: { - Map leftValue = left.getMapValue(); - Map rightValue = right.getMapValue(); - result = leftValue.equals(rightValue); - } - break; - case BOOLEAN: { - Boolean leftValue = left.getBooleanValue(); - Boolean rightValue = right.getBooleanValue(); - result = leftValue.equals(rightValue); - } - break; - case NULL: { - result = (left.getNullValue() == right.getNullValue()); - } - break; - default: { - throw new IllegalStateException(String.format("unexpected token type: %s", - tokenType)); - } - } - if (opValue.equals("!=")) { // negate the sense of the test - result = !result; - } - } - break; - case "<": - case ">=": { - switch (tokenType) { - case STRING: { - String leftValue = left.getStringValue(); - String rightValue = right.getStringValue(); - result = leftValue.compareTo(rightValue) < 0; - } - break; - case INTEGER: { - Long leftValue = left.getLongValue(); - Long rightValue = right.getLongValue(); - result = leftValue < rightValue; - } - break; - case REAL: { - Double leftValue = left.getDoubleValue(); - Double rightValue = right.getDoubleValue(); - result = leftValue < rightValue; - } - break; - case ARRAY: - case MAP: - case BOOLEAN: - case NULL: { - throw new InvalidRuleException(String.format(invalidOp, opValue, tokenType)); - } - default: { - throw new IllegalStateException(String.format("unexpected token type: %s", - tokenType)); - } - } - if (opValue.equals(">=")) { // negate the sense of the test - result = !result; - } - } - break; - case ">": - case "<=": { - switch (tokenType) { - case STRING: { - String leftValue = left.getStringValue(); - String rightValue = right.getStringValue(); - result = leftValue.compareTo(rightValue) > 0; - } - break; - case INTEGER: { - Long leftValue = left.getLongValue(); - Long rightValue = right.getLongValue(); - result = leftValue > rightValue; - } - break; - case REAL: { - Double leftValue = left.getDoubleValue(); - Double rightValue = right.getDoubleValue(); - result = leftValue > rightValue; - } - break; - case ARRAY: - case MAP: - case BOOLEAN: - case NULL: { - throw new InvalidRuleException(String.format(invalidOp, opValue, tokenType)); - } - default: { - throw new IllegalStateException(String.format("unexpected token type: %s", - tokenType)); - } - } - if (opValue.equals("<=")) { // negate the sense of the test - result = !result; - } - } - break; - default: { - throw new InvalidRuleException(String.format( - "verb '%s' has unknown comparison operator '%s'", verb, op.getObjectValue())); - } - } - this.success = result; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("%s verb='%s' success=%s left=%s op='%s' right=%s", - statementId(namespace), verb, this.success, left.getObjectValue(), - op.getObjectValue(), right.getObjectValue())); - } - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbRegexp(String verb, Map namespace, - List statement) { - Token string = getParameter(verb, statement, 1, namespace, EnumSet.of(TokenType.STRING)); - Token pattern = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); - - Pattern regexp; - Matcher matcher; - - try { - regexp = Pattern.compile(pattern.getStringValue()); - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, bad regular expression pattern '%s', %s", verb, - pattern.getObjectValue(), e)); - } - matcher = regexp.matcher(string.getStringValue()); - - if (matcher.find()) { - this.success = true; - namespace.put(REGEXP_ARRAY_VARIABLE, regexpGroupList(matcher)); - namespace.put(REGEXP_MAP_VARIABLE, regexpGroupMap(pattern.getStringValue(), matcher)); - } else { - this.success = false; - namespace.put(REGEXP_ARRAY_VARIABLE, new ArrayList()); - namespace.put(REGEXP_MAP_VARIABLE, new HashMap()); - } - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format( - "%s verb='%s' success=%s string='%s' pattern='%s' %s=%s %s=%s", - statementId(namespace), verb, this.success, string.getObjectValue(), - pattern.getObjectValue(), REGEXP_ARRAY_VARIABLE, - namespace.get(REGEXP_ARRAY_VARIABLE), REGEXP_MAP_VARIABLE, - namespace.get(REGEXP_MAP_VARIABLE))); - } - - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbRegexpReplace(String verb, Map namespace, - List statement) { - Token variable = getVariable(verb, statement, 1, namespace); - Token string = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); - Token pattern = getParameter(verb, statement, 3, namespace, EnumSet.of(TokenType.STRING)); - Token replacement = getParameter(verb, statement, 4, namespace, - EnumSet.of(TokenType.STRING)); - - Pattern regexp; - Matcher matcher; - String newValue; - - try { - regexp = Pattern.compile(pattern.getStringValue()); - } catch (Exception e) { - throw new InvalidValueException(String.format( - "verb '%s' failed, bad regular expression pattern '%s', %s", verb, - pattern.getObjectValue(), e)); - } - matcher = regexp.matcher(string.getStringValue()); - - newValue = matcher.replaceAll(replacement.getStringValue()); - variable.set(newValue); - this.success = true; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format( - "%s verb='%s' success=%s variable: %s=%s string='%s' pattern='%s' replacement='%s'", - statementId(namespace), verb, this.success, variable, variable.get(), - string.getObjectValue(), pattern.getObjectValue(), replacement.getObjectValue())); - } - - return ProcessResult.STATEMENT_CONTINUE; - } - - private ProcessResult verbExit(String verb, Map namespace, - List statement) { - ProcessResult statementResult = ProcessResult.STATEMENT_CONTINUE; - - Token exitStatusParam = getParameter(verb, statement, 1, namespace, - EnumSet.of(TokenType.STRING)); - Token criteriaParam = getParameter(verb, statement, 2, namespace, - EnumSet.of(TokenType.STRING)); - String exitStatus = (exitStatusParam.getStringValue()).toLowerCase(); - String criteria = (criteriaParam.getStringValue()).toLowerCase(); - ProcessResult result; - boolean doExit; - - if (exitStatus.equals("rule_succeeds")) { - result = ProcessResult.RULE_SUCCESS; - } else if (exitStatus.equals("rule_fails")) { - result = ProcessResult.RULE_FAIL; - } else { - throw new InvalidRuleException(String.format("verb='%s' unknown exit status '%s'", - verb, exitStatus)); - } - - if (criteria.equals("if_success")) { - if (this.success) { - doExit = true; - } else { - doExit = false; - } - } else if (criteria.equals("if_not_success")) { - if (!this.success) { - doExit = true; - } else { - doExit = false; - } - } else if (criteria.equals("always")) { - doExit = true; - } else if (criteria.equals("never")) { - doExit = false; - } else { - throw new InvalidRuleException(String.format("verb='%s' unknown exit criteria '%s'", - verb, criteria)); - } - - if (doExit) { - statementResult = result; - } - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format( - "%s verb='%s' success=%s status=%s criteria=%s exiting=%s result=%s", - statementId(namespace), verb, this.success, exitStatus, criteria, doExit, - statementResult)); - } - - return statementResult; - } - - private ProcessResult verbContinue(String verb, Map namespace, - List statement) { - ProcessResult statementResult = ProcessResult.STATEMENT_CONTINUE; - Token criteriaParam = getParameter(verb, statement, 1, namespace, - EnumSet.of(TokenType.STRING)); - String criteria = (criteriaParam.getStringValue()).toLowerCase(); - boolean doContinue; - - if (criteria.equals("if_success")) { - if (this.success) { - doContinue = true; - } else { - doContinue = false; - } - } else if (criteria.equals("if_not_success")) { - if (!this.success) { - doContinue = true; - } else { - doContinue = false; - } - } else if (criteria.equals("always")) { - doContinue = true; - } else if (criteria.equals("never")) { - doContinue = false; - } else { - throw new InvalidRuleException(String.format( - "verb='%s' unknown continue criteria '%s'", verb, criteria)); - } - - if (doContinue) { - statementResult = ProcessResult.BLOCK_CONTINUE; - } - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format( - "%s verb='%s' success=%s criteria=%s continuing=%s result=%s", - statementId(namespace), verb, this.success, criteria, doContinue, - statementResult)); - } - - return statementResult; - } - -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/StatementErrorException.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/StatementErrorException.java deleted file mode 100644 index 6abab3ee..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/StatementErrorException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -/** - * Exception thrown when a mapping rule statement fails. - * - * @author John Dennis <jdennis@redhat.com> - */ - -public class StatementErrorException extends RuntimeException { - - private static final long serialVersionUID = 8312665727576018327L; - - public StatementErrorException() { - } - - public StatementErrorException(String message) { - super(message); - } - - public StatementErrorException(Throwable cause) { - super(cause); - } - - public StatementErrorException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Token.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Token.java deleted file mode 100644 index 402fb064..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Token.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -enum TokenStorageType { - UNKNOWN, CONSTANT, VARIABLE -} - -enum TokenType { - STRING, // java String - ARRAY, // java List - MAP, // java Map - INTEGER, // java Long - BOOLEAN, // java Boolean - NULL, // java null - REAL, // java Double - UNKNOWN, // undefined -} - -/** - * Rule statements can contain variables or constants, this class encapsulates - * those values, enforces type handling and supports reading and writing of - * those values. - * - * Technically at the syntactic level these are not tokens. A token would have - * finer granularity such as identifier, operator, etc. I just couldn't think of - * a better name for how they're used here and thought token was a reasonable - * compromise as a name. - * - * @author John Dennis - */ - -class Token { - - /* - * Regexp to identify a variable beginning with $ Supports array notation, - * e.g. $foo[bar] Optional delimiting braces may be used to separate - * variable from surrounding text. - * - * Examples: $foo ${foo} $foo[bar] ${foo[bar] where foo is the variable name - * and bar is the array index. - * - * Identifer is any alphabetic followed by alphanumeric or underscore - */ - private static final String VARIABLE_PAT = "(? namespace = null; - public TokenStorageType storageType = TokenStorageType.UNKNOWN; - public TokenType type = TokenType.UNKNOWN; - public String name = null; - public String index = null; - - Token(Object input, Map namespace) { - this.namespace = namespace; - if (input instanceof String) { - parseVariable((String) input); - if (this.storageType == TokenStorageType.CONSTANT) { - this.value = input; - this.type = classify(input); - } - } else { - this.storageType = TokenStorageType.CONSTANT; - this.value = input; - this.type = classify(input); - } - } - - @Override - public String toString() { - if (this.storageType == TokenStorageType.CONSTANT) { - return String.format("%s", this.value); - } else if (this.storageType == TokenStorageType.VARIABLE) { - if (this.index == null) { - return String.format("$%s", this.name); - } else { - return String.format("$%s[%s]", this.name, this.index); - } - } else { - return "UNKNOWN"; - } - } - - void parseVariable(String string) { - Matcher matcher = VARIABLE_ONLY_RE.matcher(string); - if (matcher.find()) { - String name = matcher.group(1); - String index = matcher.group(3); - - this.storageType = TokenStorageType.VARIABLE; - this.name = name; - this.index = index; - } else { - this.storageType = TokenStorageType.CONSTANT; - } - } - - public static TokenType classify(Object value) { - TokenType tokenType = TokenType.UNKNOWN; - // ordered by expected occurrence - if (value instanceof String) { - tokenType = TokenType.STRING; - } else if (value instanceof List) { - tokenType = TokenType.ARRAY; - } else if (value instanceof Map) { - tokenType = TokenType.MAP; - } else if (value instanceof Long) { - tokenType = TokenType.INTEGER; - } else if (value instanceof Boolean) { - tokenType = TokenType.BOOLEAN; - } else if (value == null) { - tokenType = TokenType.NULL; - } else if (value instanceof Double) { - tokenType = TokenType.REAL; - } else { - throw new InvalidRuleException(String.format( - "Type must be String, Long, Double, Boolean, List, Map, or null, not %s", - value.getClass().getSimpleName(), value)); - } - return tokenType; - } - - Object get() { - return get(null); - } - - Object get(Object index) { - Object base = null; - - if (this.storageType == TokenStorageType.CONSTANT) { - return this.value; - } - - if (this.namespace.containsKey(this.name)) { - base = this.namespace.get(this.name); - } else { - throw new UndefinedValueException(String.format("variable '%s' not defined", this.name)); - } - - if (index == null) { - index = this.index; - } - - if (index == null) { // scalar types - value = base; - } else { - if (base instanceof List) { - @SuppressWarnings("unchecked") - List list = (List) base; - Integer idx = null; - - if (index instanceof Long) { - idx = new Integer(((Long) index).intValue()); - } else if (index instanceof String) { - try { - idx = new Integer((String) index); - } catch (NumberFormatException e) { - throw new InvalidTypeException( - String.format( - "variable '%s' is an array indexed by '%s', however the index cannot be converted to an integer", - this.name, index, e)); - } - } else { - throw new InvalidTypeException( - String.format( - "variable '%s' is an array indexed by '%s', however the index must be an integer or string not %s", - this.name, index, index.getClass().getSimpleName())); - } - - try { - value = list.get(idx); - } catch (IndexOutOfBoundsException e) { - throw new UndefinedValueException( - String.format( - "variable '%s' is an array of size %d indexed by '%s', however the index is out of bounds", - this.name, list.size(), idx, e)); - } - } else if (base instanceof Map) { - @SuppressWarnings("unchecked") - Map map = (Map) base; - String idx = null; - if (index instanceof String) { - idx = (String) index; - } else { - throw new InvalidTypeException( - String.format( - "variable '%s' is a map indexed by '%s', however the index must be a string not %s", - this.name, index, index.getClass().getSimpleName())); - } - if (!map.containsKey(idx)) { - throw new UndefinedValueException( - String.format( - "variable '%s' is a map indexed by '%s', however the index does not exist", - this.name, index)); - } - value = map.get(idx); - } else { - throw new InvalidTypeException( - String.format( - "variable '%s' is indexed by '%s', variable must be an array or map, not %s", - this.name, index, base.getClass().getSimpleName())); - - } - } - this.type = classify(value); - return value; - } - - void set(Object value) { - set(value, null); - } - - void set(Object value, Object index) { - - if (this.storageType == TokenStorageType.CONSTANT) { - throw new InvalidTypeException("cannot assign to a constant"); - } - - if (index == null) { - index = this.index; - } - - if (index == null) { // scalar types - this.namespace.put(this.name, value); - } else { - Object base = null; - - if (this.namespace.containsKey(this.name)) { - base = this.namespace.get(this.name); - } else { - throw new UndefinedValueException(String.format("variable '%s' not defined", - this.name)); - } - - if (base instanceof List) { - @SuppressWarnings("unchecked") - List list = (List) base; - Integer idx = null; - - if (index instanceof Long) { - idx = new Integer(((Long) index).intValue()); - } else if (index instanceof String) { - try { - idx = new Integer((String) index); - } catch (NumberFormatException e) { - throw new InvalidTypeException( - String.format( - "variable '%s' is an array indexed by '%s', however the index cannot be converted to an integer", - this.name, index, e)); - } - } else { - throw new InvalidTypeException( - String.format( - "variable '%s' is an array indexed by '%s', however the index must be an integer or string not %s", - this.name, index, index.getClass().getSimpleName())); - } - - try { - value = list.set(idx, value); - } catch (IndexOutOfBoundsException e) { - throw new UndefinedValueException( - String.format( - "variable '%s' is an array of size %d indexed by '%s', however the index is out of bounds", - this.name, list.size(), idx, e)); - } - } else if (base instanceof Map) { - @SuppressWarnings("unchecked") - Map map = (Map) base; - String idx = null; - if (index instanceof String) { - idx = (String) index; - } else { - throw new InvalidTypeException( - String.format( - "variable '%s' is a map indexed by '%s', however the index must be a string not %s", - this.name, index, index.getClass().getSimpleName())); - } - if (!map.containsKey(idx)) { - throw new UndefinedValueException( - String.format( - "variable '%s' is a map indexed by '%s', however the index does not exist", - this.name, index)); - } - value = map.put(idx, value); - } else { - throw new InvalidTypeException( - String.format( - "variable '%s' is indexed by '%s', variable must be an array or map, not %s", - this.name, index, base.getClass().getSimpleName())); - - } - } - } - - public Object load() { - this.value = get(); - return this.value; - } - - public Object load(Object index) { - this.value = get(index); - return this.value; - } - - public String getStringValue() { - if (this.type == TokenType.STRING) { - return (String) this.value; - } else { - throw new InvalidTypeException(String.format("expected %s value but token type is %s", - TokenType.STRING, this.type)); - } - } - - public List getListValue() { - if (this.type == TokenType.ARRAY) { - @SuppressWarnings("unchecked") - List list = (List) this.value; - return list; - } else { - throw new InvalidTypeException(String.format("expected %s value but token type is %s", - TokenType.ARRAY, this.type)); - } - } - - public Map getMapValue() { - if (this.type == TokenType.MAP) { - @SuppressWarnings("unchecked") - Map map = (Map) this.value; - return map; - } else { - throw new InvalidTypeException(String.format("expected %s value but token type is %s", - TokenType.MAP, this.type)); - } - } - - public Long getLongValue() { - if (this.type == TokenType.INTEGER) { - return (Long) this.value; - } else { - throw new InvalidTypeException(String.format("expected %s value but token type is %s", - TokenType.INTEGER, this.type)); - } - } - - public Boolean getBooleanValue() { - if (this.type == TokenType.BOOLEAN) { - return (Boolean) this.value; - } else { - throw new InvalidTypeException(String.format("expected %s value but token type is %s", - TokenType.BOOLEAN, this.type)); - } - } - - public Double getDoubleValue() { - if (this.type == TokenType.REAL) { - return (Double) this.value; - } else { - throw new InvalidTypeException(String.format("expected %s value but token type is %s", - TokenType.REAL, this.type)); - } - } - - public Object getNullValue() { - if (this.type == TokenType.NULL) { - return this.value; - } else { - throw new InvalidTypeException(String.format("expected %s value but token type is %s", - TokenType.NULL, this.type)); - } - } - - public Object getObjectValue() { - return this.value; - } -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/UndefinedValueException.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/UndefinedValueException.java deleted file mode 100644 index 7200da3d..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/UndefinedValueException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2014 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.idpmapping; - -/** - * Exception thrown when a statement references an undefined value. - * - * @author John Dennis <jdennis@redhat.com> - */ - -public class UndefinedValueException extends RuntimeException { - - private static final long serialVersionUID = -1607453931670834435L; - - public UndefinedValueException() { - } - - public UndefinedValueException(String message) { - super(message); - } - - public UndefinedValueException(Throwable cause) { - super(cause); - } - - public UndefinedValueException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/RuleProcessorTest.java b/odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/RuleProcessorTest.java deleted file mode 100644 index 84d403f9..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/RuleProcessorTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2016 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.api.support.membermodification.MemberMatcher; -import org.powermock.api.support.membermodification.MemberModifier; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.reflect.Whitebox; - -@PrepareForTest(RuleProcessor.class) -@RunWith(PowerMockRunner.class) -public class RuleProcessorTest { - - @Mock - private RuleProcessor ruleProcess; - - @Before - public void setUp() { - ruleProcess = PowerMockito.mock(RuleProcessor.class, Mockito.CALLS_REAL_METHODS); - } - - @Test - public void testJoin() { - List list = new ArrayList(); - list.add("str1"); - list.add("str2"); - list.add("str3"); - assertEquals("str1/str2/str3", RuleProcessor.join(list, "/")); - } - - @Test - public void testSubstituteVariables() { - Map namespace = new HashMap() { - { - put("foo1", new HashMap() { - { - put("0", "1"); - } - }); - } - }; - String str = "foo1[0]"; - String subVariable = ruleProcess.substituteVariables(str, namespace); - assertNotNull(subVariable); - assertEquals(subVariable, str); - } - - @Test - public void testGetMapping() { - Map namespace = new HashMap() { - { - put("foo1", new HashMap() { - { - put("0", "1"); - } - }); - } - }; - final Map item = new HashMap() { - { - put("str", "val"); - } - }; - Map rules = new HashMap() { - { - put("mapping", item); - put("mapping_name", "mapping"); - } - }; - Map mapping = ruleProcess.getMapping(namespace, rules); - assertNotNull(mapping); - assertTrue(mapping.containsKey("str")); - assertEquals("val", mapping.get("str")); - } - - @Test - public void testProcess() throws Exception { - String json = " {\"rules\":[" + "{\"Name\":\"user\", \"Id\":1}," - + "{\"Name\":\"Admin\", \"Id\":2}]} "; - Map mapping = new HashMap() { - { - put("Name", "Admin"); - } - }; - List> internalRules = new ArrayList>(); - Map internalRule = new HashMap() { - { - put("Name", "Admin"); - put("statement_blocks", "user"); - } - }; - internalRules.add(internalRule); - MemberModifier.field(RuleProcessor.class, "rules").set(ruleProcess, internalRules); - PowerMockito.suppress(MemberMatcher.method(RuleProcessor.class, "processRule", Map.class, - Map.class)); - PowerMockito.when(ruleProcess, "processRule", any(Map.class), any(Map.class)).thenReturn( - ProcessResult.RULE_SUCCESS); - PowerMockito.suppress(MemberMatcher.method(RuleProcessor.class, "getMapping", Map.class, - Map.class)); - when(ruleProcess.getMapping(any(Map.class), any(Map.class))).thenReturn(mapping); - Whitebox.invokeMethod(ruleProcess, "process", json); - verify(ruleProcess, times(3)).getMapping(any(Map.class), any(Map.class)); - } - -} diff --git a/odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/TokenTest.java b/odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/TokenTest.java deleted file mode 100644 index d6181051..00000000 --- a/odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/TokenTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2016 Red Hat, Inc. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.idpmapping; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import org.junit.Test; - -public class TokenTest { - - private final Map namespace = new HashMap() { - { - put("foo1", new HashMap() { - { - put("0", "1"); - } - }); - } - }; - private Object input = "$foo1[0]"; - private Token token = new Token(input, namespace); - private Token mapToken = new Token(namespace, namespace); - - @Test - public void testToken() { - assertEquals(token.toString(), input); - assertTrue(token.storageType == TokenStorageType.VARIABLE); - assertEquals(mapToken.toString(), "{foo1={0=1}}"); - assertTrue(mapToken.storageType == TokenStorageType.CONSTANT); - } - - @Test - public void testClassify() { - assertEquals(Token.classify(new ArrayList<>()), TokenType.ARRAY); - assertEquals(Token.classify(true), TokenType.BOOLEAN); - assertEquals(Token.classify(new Long(365)), TokenType.INTEGER); - assertEquals(Token.classify(new HashMap()), TokenType.MAP); - assertEquals(Token.classify(null), TokenType.NULL); - assertEquals(Token.classify(365.00), TokenType.REAL); - assertEquals(Token.classify("foo_str"), TokenType.STRING); - } - - @Test - public void testGet() { - assertNotNull(token.get()); - assertTrue(token.get("0") == "1"); - assertNotNull(mapToken.get()); - assertTrue(mapToken.get(0) == namespace); - } - - @Test - public void testGetMapValue() { - assertTrue(mapToken.getMapValue() == namespace); - } -} diff --git a/odl-aaa-moon/aaa-shiro-act/pom.xml b/odl-aaa-moon/aaa-shiro-act/pom.xml deleted file mode 100644 index d8507c6d..00000000 --- a/odl-aaa-moon/aaa-shiro-act/pom.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-shiro-act - bundle - - - - org.opendaylight.aaa - aaa-shiro - - - org.apache.felix - org.apache.felix.dependencymanager - - - - org.slf4j - slf4j-api - - - commons-beanutils - commons-beanutils - 1.8.3 - - - - - junit - junit - test - - - org.mockito - mockito-all - test - - - - - - - org.apache.felix - maven-bundle-plugin - ${bundle.plugin.version} - true - - - ${project.groupId}.${project.artifactId} - - ${project.basedir}/META-INF - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - org.opendaylight.aaa.shiroact.Activator - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - diff --git a/odl-aaa-moon/aaa-shiro-act/src/main/java/org/opendaylight/aaa/shiroact/Activator.java b/odl-aaa-moon/aaa-shiro-act/src/main/java/org/opendaylight/aaa/shiroact/Activator.java deleted file mode 100644 index 0012a0bd..00000000 --- a/odl-aaa-moon/aaa-shiro-act/src/main/java/org/opendaylight/aaa/shiroact/Activator.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiroact; - -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.opendaylight.aaa.shiro.ServiceProxy; -import org.osgi.framework.BundleContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Responsible for activating the aaa-shiro-act bundle. This bundle is primarily - * responsible for enabling AuthN and AuthZ. If this bundle is not installed, - * then AuthN and AuthZ will not take effect. - * - * To ensure that the AAA is enabled for your feature, make sure to include the - * odl-aaa-shiro feature in your feature definition. - * - * Offers contextual DEBUG level clues concerning the activation of - * the aaa-shiro-act bundle. To enable the enhanced debugging issue - * the following line in the karaf shell: - * log:set debug org.opendaylight.aaa.shiroact.Activator - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -public class Activator extends DependencyActivatorBase { - - private static final Logger LOG = LoggerFactory.getLogger(Activator.class); - - @Override - public void destroy(BundleContext bc, DependencyManager dm) - throws Exception { - final String DEBUG_MESSAGE = "Destroying the aaa-shiro-act bundle"; - LOG.debug(DEBUG_MESSAGE); - } - - @Override - public void init(BundleContext bc, DependencyManager dm) throws Exception { - final String DEBUG_MESSAGE = "Initializing the aaa-shiro-act bundle"; - LOG.debug(DEBUG_MESSAGE); - ServiceProxy.getInstance().setEnabled(true); - } - -} diff --git a/odl-aaa-moon/aaa-shiro-act/src/test/java/org/opendaylight/aaa/shiroact/ActivatorTest.java b/odl-aaa-moon/aaa-shiro-act/src/test/java/org/opendaylight/aaa/shiroact/ActivatorTest.java deleted file mode 100644 index 23eef9db..00000000 --- a/odl-aaa-moon/aaa-shiro-act/src/test/java/org/opendaylight/aaa/shiroact/ActivatorTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiroact; - -import static org.junit.Assert.*; - -import org.junit.Test; -import org.opendaylight.aaa.shiro.ServiceProxy; - -public class ActivatorTest { - - @Test - public void testActivatorEnablesServiceProxy() throws Exception { - // should toggle the ServiceProxy enable status to true - new Activator().init(null, null);; - assertTrue(ServiceProxy.getInstance().getEnabled(null)); - } - -} diff --git a/odl-aaa-moon/aaa-shiro/pom.xml b/odl-aaa-moon/aaa-shiro/pom.xml deleted file mode 100644 index 2f848215..00000000 --- a/odl-aaa-moon/aaa-shiro/pom.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - aaa-shiro - bundle - - - - - com.sun.jersey - jersey-client - provided - - - org.json - json - 20140107 - - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - provided - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.common - provided - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.resourceserver - provided - - - org.apache.felix - org.apache.felix.dependencymanager - - - org.opendaylight.aaa - aaa-authn-sts - - - org.opendaylight.aaa - aaa-authn-basic - - - org.apache.shiro - shiro-core - - - org.apache.shiro - shiro-web - - - org.slf4j - slf4j-api - - - commons-beanutils - commons-beanutils - 1.8.3 - - - javax.servlet - javax.servlet-api - - - com.google.guava - guava - - - - - junit - junit - test - - - org.mockito - mockito-all - test - - - - - - - org.apache.felix - maven-bundle-plugin - ${bundle.plugin.version} - true - - - ${project.groupId}.${project.artifactId} - - ${project.basedir}/META-INF - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - - * - - /moon - org.opendaylight.aaa.shiro.Activator - - - - - org.apache.maven.plugins - maven-jar-plugin - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - package - - attach-artifact - - - - - ${project.build.directory}/classes/shiro.ini - cfg - configuration - - - - - - - - - diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java deleted file mode 100644 index 2f1c98f7..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro; - -import org.apache.felix.dm.DependencyActivatorBase; -import org.apache.felix.dm.DependencyManager; -import org.osgi.framework.BundleContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This scaffolding allows the use of AAA Filters without AuthN or AuthZ - * enabled. This is done to support workflows such as those included in the - * odl-restconf-noauth feature. - * - * This class is also responsible for offering contextual DEBUG - * level clues concerning the activation of the aaa-shiro bundle. - * To enable these debug messages, issue the following command in the karaf - * shell: log:set debug org.opendaylight.aaa.shiro.Activator - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -public class Activator extends DependencyActivatorBase { - - private static final Logger LOG = LoggerFactory.getLogger(Activator.class); - - @Override - public void destroy(BundleContext bc, DependencyManager dm) throws Exception { - final String DEBUG_MESSAGE = "Destroying the aaa-shiro bundle"; - LOG.debug(DEBUG_MESSAGE); - } - - @Override - public void init(BundleContext bc, DependencyManager dm) throws Exception { - final String DEBUG_MESSAGE = "Initializing the aaa-shiro bundle"; - LOG.debug(DEBUG_MESSAGE); - } - -} diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java deleted file mode 100644 index e4485d73..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro; - -import org.opendaylight.aaa.shiro.filters.AAAFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Responsible for enabling and disabling the AAA service. By default, the - * service is disabled; the AAAFilter will not require AuthN or AuthZ. The - * service is enabled through calling - * ServiceProxy.getInstance().setEnabled(true). AuthN and AuthZ are - * disabled by default in order to support workflows such as the feature - * odl-restconf-noauth. - * - * The AAA service is enabled through installing the odl-aaa-shiro - * feature. The org.opendaylight.aaa.shiroact.Activator() - * constructor calls enables AAA through the ServiceProxy, which in turn enables - * the AAAFilter. - * - * ServiceProxy is a singleton; access to the ServiceProxy is granted through - * the getInstance() function. - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - * @see resconf - * web,xml - * @see org.opendaylight.aaa.shiro.Activator - * @see org.opendaylight.aaa.shiro.filters.AAAFilter - */ -public class ServiceProxy { - private static final Logger LOG = LoggerFactory.getLogger(ServiceProxy.class); - - /** - * AuthN and AuthZ are disabled by default to support workflows included in - * features such as odl-restconf-noauth - */ - public static final boolean DEFAULT_AA_ENABLE_STATUS = false; - - private static ServiceProxy instance = new ServiceProxy(); - private volatile boolean enabled = false; - private AAAFilter filter; - - /** - * private for singleton pattern - */ - private ServiceProxy() { - final String INFO_MESSAGE = "Creating the ServiceProxy"; - LOG.info(INFO_MESSAGE); - } - - /** - * @return ServiceProxy, a feature level singleton - */ - public static ServiceProxy getInstance() { - return instance; - } - - /** - * Enables/disables the feature, cascading the state information to the - * AAAFilter. - * - * @param enabled A flag indicating whether to enable the Service. - */ - public synchronized void setEnabled(final boolean enabled) { - this.enabled = enabled; - final String SERVICE_ENABLED_INFO_MESSAGE = "Setting ServiceProxy enabled to " + enabled; - LOG.info(SERVICE_ENABLED_INFO_MESSAGE); - // check for null because of non-determinism in bundle load - if (filter != null) { - filter.setEnabled(enabled); - } - } - - /** - * Extract whether the service is enabled. - * - * @param filter - * register an optional Filter for callback if enable state - * changes - * @return Whether the service is enabled - */ - public synchronized boolean getEnabled(final AAAFilter filter) { - this.filter = filter; - return enabled; - } -} diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java deleted file mode 100644 index e768ea59..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.shiro.accounting; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Accounter is a common place to output AAA messages. Use this class through - * invoking Logger.output("message"). - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -public class Accounter { - - private static final Logger LOG = LoggerFactory.getLogger(Accounter.class); - - /* - * Essentially makes Accounter a singleton, avoiding the verbosity of - * Accounter.getInstance().output("message"). - */ - private Accounter() { - } - - /** - * Account for a particular message - * - * @param message A message for the aggregated AAA log. - */ - public static void output(final String message) { - LOG.debug(message); - } -} diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java deleted file mode 100644 index 9e84c988..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.shiro.authorization; - -import com.google.common.collect.Sets; -import java.util.Collection; -import java.util.HashSet; - -/** - * A singleton container of default authorization rules that are installed as - * part of Shiro initialization. This class defines an immutable set of rules - * that are needed to provide system-wide security. These include protecting - * certain MD-SAL leaf nodes that contain AAA data from random access. This is - * not a place to define your custom rule set; additional RBAC rules are - * configured through the shiro initialization file: - * $KARAF_HOME/shiro.ini - * - * An important distinction to consider is that Shiro URL rules work to protect - * the system at the Web layer, and AuthzDomDataBroker works to - * protect the system down further at the DOM layer. - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - * - */ -public class DefaultRBACRules { - - private static DefaultRBACRules instance; - - /** - * a collection of the default security rules - */ - private Collection rbacRules = new HashSet(); - - /** - * protects the AAA MD-SAL store by preventing access to the leaf nodes to - * non-admin users. - */ - private static final RBACRule PROTECT_AAA_MDSAL = RBACRule.createAuthorizationRule( - "*/authorization/*", Sets.newHashSet("admin")); - - /* - * private for singleton pattern - */ - private DefaultRBACRules() { - // rbacRules.add(PROTECT_AAA_MDSAL); - } - - /** - * - * @return the container instance for the default RBAC Rules - */ - public static final DefaultRBACRules getInstance() { - if (null == instance) { - instance = new DefaultRBACRules(); - } - return instance; - } - - /** - * - * @return a copy of the default rules, so any modifications to the returned - * reference do not affect the DefaultRBACRules. - */ - public final Collection getRBACRules() { - // Returns a copy of the rbacRules set such that the original set keeps - // its contract of remaining immutable. Calls to rbacRules.add() are - // encapsulated solely in DefaultRBACRules. - // - // Since this method is only called at shiro initialiation time, - // memory consumption of creating a new set is a non-issue. - return Sets.newHashSet(rbacRules); - } -} diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java deleted file mode 100644 index 0da95eb4..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.shiro.authorization; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Sets; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A container for RBAC Rules. An RBAC Rule is composed of a url pattern which - * may contain asterisk characters (*), and a collection of roles. These are - * represented in shiro.ini in the following format: - * urlPattern=roles[atLeastOneCommaSeperatedRole] - * - * RBACRules are immutable; that is, you cannot change the url pattern or the - * roles after creation. This is done for security purposes. RBACRules are - * created through utilizing a static factory method: - * RBACRule.createRBACRule() - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - * - */ -public class RBACRule { - - private static final Logger LOG = LoggerFactory.getLogger(RBACRule.class); - - /** - * a url pattern that can optional contain asterisk characters (*) - */ - private String urlPattern; - - /** - * a collection of role names, such as "admin" and "user" - */ - private Collection roles = new HashSet(); - - /** - * Creates an RBAC Rule. Made private for static factory method. - * - * @param urlPattern - * Cannot be null or the empty string. - * @param roles - * Must contain at least one role. - * @throws NullPointerException - * if urlPattern or roles is null - * @throws IllegalArgumentException - * if urlPattern is an empty string or - * roles is an empty collection. - */ - private RBACRule(final String urlPattern, final Collection roles) - throws NullPointerException, IllegalArgumentException { - - this.setUrlPattern(urlPattern); - this.setRoles(roles); - } - - /** - * The static factory method used to create RBACRules. - * - * @param urlPattern - * Cannot be null or the empty string. - * @param roles - * Cannot be null or an emtpy collection. - * @return An immutable RBACRule - */ - public static RBACRule createAuthorizationRule(final String urlPattern, - final Collection roles) { - - RBACRule authorizationRule = null; - try { - authorizationRule = new RBACRule(urlPattern, roles); - } catch (Exception e) { - LOG.error("Cannot instantiate the AuthorizationRule", e); - } - return authorizationRule; - } - - /** - * - * @return the urlPattern for the RBACRule - */ - public String getUrlPattern() { - return urlPattern; - } - - /* - * helper to ensure the url pattern is not the empty string - */ - private static void checkUrlPatternLength(final String urlPattern) - throws IllegalArgumentException { - - final String EXCEPTION_MESSAGE = "Empty String is not allowed for urlPattern"; - if (urlPattern.isEmpty()) { - throw new IllegalArgumentException(EXCEPTION_MESSAGE); - } - } - - private void setUrlPattern(final String urlPattern) throws NullPointerException, - IllegalArgumentException { - - Preconditions.checkNotNull(urlPattern); - checkUrlPatternLength(urlPattern); - this.urlPattern = urlPattern; - } - - /** - * - * @return a copy of the rule, so any modifications to the returned - * reference do not affect the immutable RBACRule. - */ - public Collection getRoles() { - // Returns a copy of the roles collection such that the original set - // keeps - // its contract of remaining immutable. - // - // Since this method is only called at shiro initialiation time, - // memory consumption of creating a new set is a non-issue. - return Sets.newHashSet(roles); - } - - /* - * check to ensure the roles collection is not empty - */ - private static void checkRolesCollectionSize(final Collection roles) - throws IllegalArgumentException { - - final String EXCEPTION_MESSAGE = "roles must contain at least 1 role"; - if (roles.isEmpty()) { - throw new IllegalArgumentException(EXCEPTION_MESSAGE); - } - } - - private void setRoles(final Collection roles) throws NullPointerException, - IllegalArgumentException { - - Preconditions.checkNotNull(roles); - checkRolesCollectionSize(roles); - this.roles = roles; - } - - /** - * Generates a string representation of the RBACRule roles in - * shiro form. - * - * @return roles string representation in the form - * roles[roleOne,roleTwo] - */ - public String getRolesInShiroFormat() { - final String ROLES_STRING = "roles"; - return ROLES_STRING + Arrays.toString(roles.toArray()); - } - - /** - * Generates the string representation of the RBACRule in shiro - * form. For example: urlPattern=roles[admin,user] - */ - @Override - public String toString() { - return String.format("%s=%s", urlPattern, getRolesInShiroFormat()); - } -} diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java deleted file mode 100644 index b53588d8..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro.filters; - -import org.apache.shiro.web.servlet.ShiroFilter; -import org.opendaylight.aaa.shiro.ServiceProxy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The default AAA JAX-RS 1.X Web Filter. This class is also responsible for - * delivering debug information; to enable these debug statements, please issue - * the following in the karaf shell: - * - * log:set debug org.opendaylight.aaa.shiro.filters.AAAFilter - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - * @see javax.servlet.Filter - * @see org.apache.shiro.web.servlet.ShiroFilter - */ -public class AAAFilter extends ShiroFilter { - - private static final Logger LOG = LoggerFactory.getLogger(AAAFilter.class); - - public AAAFilter() { - super(); - final String DEBUG_MESSAGE = "Creating the AAAFilter"; - LOG.debug(DEBUG_MESSAGE); - } - - /* - * (non-Javadoc) - * - * Adds context clues that aid in debugging. Also initializes the enable - * status to correspond with - * ServiceProxy.getInstance.getEnabled(). - * - * @see org.apache.shiro.web.servlet.ShiroFilter#init() - */ - @Override - public void init() throws Exception { - super.init(); - final String DEBUG_MESSAGE = "Initializing the AAAFilter"; - LOG.debug(DEBUG_MESSAGE); - // sets the filter to the startup value. Because of non-determinism in - // bundle loading, this passes an instance of itself along so that if - // the - // enable status changes, then AAAFilter enable status is changed. - setEnabled(ServiceProxy.getInstance().getEnabled(this)); - } - - /* - * (non-Javadoc) - * - * Adds context clues to aid in debugging whether the filter is enabled. - * - * @see - * org.apache.shiro.web.servlet.OncePerRequestFilter#setEnabled(boolean) - */ - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - final String DEBUG_MESSAGE = "Setting AAAFilter enabled to " + enabled; - LOG.debug(DEBUG_MESSAGE); - } -} diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java deleted file mode 100644 index 06038c54..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.shiro.filters; - - -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static javax.servlet.http.HttpServletResponse.SC_CREATED; -import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; -import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.oltu.oauth2.as.response.OAuthASResponse; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.OAuthResponse; -import org.apache.oltu.oauth2.common.message.types.TokenType; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.UsernamePasswordToken; -import org.apache.shiro.subject.Subject; -import org.apache.shiro.web.filter.authc.AuthenticatingFilter; -import org.opendaylight.aaa.AuthenticationBuilder; -import org.opendaylight.aaa.ClaimBuilder; -import org.opendaylight.aaa.api.Authentication; -import org.opendaylight.aaa.api.Claim; -import org.opendaylight.aaa.shiro.moon.MoonPrincipal; -import org.opendaylight.aaa.sts.OAuthRequest; -import org.opendaylight.aaa.sts.ServiceLocator; - - -public class MoonOAuthFilter extends AuthenticatingFilter{ - - private static final String DOMAIN_SCOPE_REQUIRED = "Domain scope required"; - private static final String NOT_IMPLEMENTED = "not_implemented"; - private static final String UNAUTHORIZED = "unauthorized"; - private static final String UNAUTHORIZED_CREDENTIALS = "Unauthorized: Login/Password incorrect"; - - static final String TOKEN_GRANT_ENDPOINT = "/token"; - static final String TOKEN_REVOKE_ENDPOINT = "/revoke"; - static final String TOKEN_VALIDATE_ENDPOINT = "/validate"; - - @Override - protected UsernamePasswordToken createToken(ServletRequest request, ServletResponse response) throws Exception { - // TODO Auto-generated method stub - HttpServletRequest httpRequest = (HttpServletRequest) request; - OAuthRequest oauthRequest = new OAuthRequest(httpRequest); - return new UsernamePasswordToken(oauthRequest.getUsername(),oauthRequest.getPassword()); - } - - @Override - protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { - // TODO Auto-generated method stub - Subject currentUser = SecurityUtils.getSubject(); - return executeLogin(request, response); - } - - protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, - ServletRequest request, ServletResponse response) throws Exception { - HttpServletResponse httpResponse= (HttpServletResponse) response; - MoonPrincipal principal = (MoonPrincipal) subject.getPrincipals().getPrimaryPrincipal(); - Claim claim = principal.principalToClaim(); - oauthAccessTokenResponse(httpResponse,claim,"",principal.getToken()); - return true; - } - - protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, - ServletRequest request, ServletResponse response) { - HttpServletResponse resp = (HttpServletResponse) response; - error(resp, SC_BAD_REQUEST, UNAUTHORIZED_CREDENTIALS); - return false; - } - - protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { - /** - * Here, we will call three functions depending on whether user wants to: - * create Token - * refresh token - * delete token - */ - HttpServletRequest req= (HttpServletRequest) request; - HttpServletResponse resp = (HttpServletResponse) response; - try { - if (req.getServletPath().equals(TOKEN_GRANT_ENDPOINT)) { - UsernamePasswordToken token = createToken(request, response); - if (token == null) { - String msg = "A valid non-null AuthenticationToken " + - "must be created in order to execute a login attempt."; - throw new IllegalStateException(msg); - } - try { - Subject subject = getSubject(request, response); - subject.login(token); - return onLoginSuccess(token, subject, request, response); - } catch (AuthenticationException e) { - return onLoginFailure(token, e, request, response); - } - } else if (req.getServletPath().equals(TOKEN_REVOKE_ENDPOINT)) { - //deleteAccessToken(req, resp); - } else if (req.getServletPath().equals(TOKEN_VALIDATE_ENDPOINT)) { - //validateToken(req, resp); - } - } catch (AuthenticationException e) { - error(resp, SC_UNAUTHORIZED, e.getMessage()); - } catch (OAuthProblemException oe) { - error(resp, oe); - } catch (Exception e) { - error(resp, e); - } - return false; - } - - private void oauthAccessTokenResponse(HttpServletResponse resp, Claim claim, String clientId, String token) - throws OAuthSystemException, IOException { - if (claim == null) { - throw new AuthenticationException(UNAUTHORIZED); - } - - // Cache this token... - Authentication auth = new AuthenticationBuilder(new ClaimBuilder(claim).setClientId( - clientId).build()).setExpiration(tokenExpiration()).build(); - ServiceLocator.getInstance().getTokenStore().put(token, auth); - - OAuthResponse r = OAuthASResponse.tokenResponse(SC_CREATED).setAccessToken(token) - .setTokenType(TokenType.BEARER.toString()) - .setExpiresIn(Long.toString(auth.expiration())) - .buildJSONMessage(); - write(resp, r); - } - - private void write(HttpServletResponse resp, OAuthResponse r) throws IOException { - resp.setStatus(r.getResponseStatus()); - PrintWriter pw = resp.getWriter(); - pw.print(r.getBody()); - pw.flush(); - pw.close(); - } - - private long tokenExpiration() { - return ServiceLocator.getInstance().getTokenStore().tokenExpiration(); - } - - // Emit an error OAuthResponse with the given HTTP code - private void error(HttpServletResponse resp, int httpCode, String error) { - try { - OAuthResponse r = OAuthResponse.errorResponse(httpCode).setError(error) - .buildJSONMessage(); - write(resp, r); - } catch (Exception e1) { - // Nothing to do here - } - } - - private void error(HttpServletResponse resp, OAuthProblemException e) { - try { - OAuthResponse r = OAuthResponse.errorResponse(SC_BAD_REQUEST).error(e) - .buildJSONMessage(); - write(resp, r); - } catch (Exception e1) { - // Nothing to do here - } - } - - private void error(HttpServletResponse resp, Exception e) { - try { - OAuthResponse r = OAuthResponse.errorResponse(SC_INTERNAL_SERVER_ERROR) - .setError(e.getClass().getName()) - .setErrorDescription(e.getMessage()).buildJSONMessage(); - write(resp, r); - } catch (Exception e1) { - // Nothing to do here - } - } - -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java deleted file mode 100644 index 90b0101e..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro.filters; - -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - -import org.apache.shiro.codec.Base64; -import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; -import org.apache.shiro.web.util.WebUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Extends BasicHttpAuthenticationFilter to include ability to - * authenticate OAuth2 tokens, which is needed for backwards compatibility with - * TokenAuthFilter. - * - * This behavior is enabled by default for backwards compatibility. To disable - * OAuth2 functionality, just comment out the following line from the - * etc/shiro.ini file: - * authcBasic = org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter - * then restart the karaf container. - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - * - */ -public class ODLHttpAuthenticationFilter extends BasicHttpAuthenticationFilter { - - private static final Logger LOG = LoggerFactory.getLogger(ODLHttpAuthenticationFilter.class); - - // defined in lower-case for more efficient string comparison - protected static final String BEARER_SCHEME = "bearer"; - - protected static final String OPTIONS_HEADER = "OPTIONS"; - - public ODLHttpAuthenticationFilter() { - super(); - LOG.info("Creating the ODLHttpAuthenticationFilter"); - } - - @Override - protected String[] getPrincipalsAndCredentials(String scheme, String encoded) { - final String decoded = Base64.decodeToString(encoded); - // attempt to decode username/password; otherwise decode as token - if (decoded.contains(":")) { - return decoded.split(":"); - } - return new String[] { encoded }; - } - - @Override - protected boolean isLoginAttempt(String authzHeader) { - final String authzScheme = getAuthzScheme().toLowerCase(); - final String authzHeaderLowerCase = authzHeader.toLowerCase(); - return authzHeaderLowerCase.startsWith(authzScheme) - || authzHeaderLowerCase.startsWith(BEARER_SCHEME); - } - - @Override - protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, - Object mappedValue) { - final HttpServletRequest httpRequest = WebUtils.toHttp(request); - final String httpMethod = httpRequest.getMethod(); - if (OPTIONS_HEADER.equalsIgnoreCase(httpMethod)) { - return true; - } else { - return super.isAccessAllowed(httpRequest, response, mappedValue); - } - } -} diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java deleted file mode 100644 index a95b4e7f..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.aaa.shiro.moon; - -import com.google.common.collect.ImmutableSet; - -import java.io.Serializable; -import java.util.Set; - -import org.opendaylight.aaa.api.Claim; - -public class MoonPrincipal { - - private final String username; - private final String domain; - private final String userId; - private final Set roles; - private final String token; - - - public MoonPrincipal(String username, String domain, String userId, Set roles, String token) { - this.username = username; - this.domain = domain; - this.userId = userId; - this.roles = roles; - this.token = token; - } - - public MoonPrincipal createODLPrincipal(String username, String domain, - String userId, Set roles, String token) { - - return new MoonPrincipal(username, domain, userId, roles,token); - } - - public Claim principalToClaim (){ - return new MoonClaim("", this.getUserId(), this.getUsername(), this.getDomain(), this.getRoles()); - } - - public String getUsername() { - return this.username; - } - - public String getDomain() { - return this.domain; - } - - public String getUserId() { - return this.userId; - } - - public Set getRoles() { - return this.roles; - } - - public String getToken(){ - return this.token; - } - - public class MoonClaim implements Claim, Serializable { - private static final long serialVersionUID = -8115027645190209125L; - private int hashCode = 0; - private String clientId; - private String userId; - private String user; - private String domain; - private ImmutableSet roles; - - public MoonClaim(String clientId, String userId, String user, String domain, Set roles) { - this.clientId = clientId; - this.userId = userId; - this.user = user; - this.domain = domain; - this.roles = ImmutableSet. builder().addAll(roles).build(); - - if (userId.isEmpty() || user.isEmpty() || roles.isEmpty() || roles.contains("")) { - throw new IllegalStateException("The Claim is missing one or more of the required fields."); - } - } - - @Override - public String clientId() { - return clientId; - } - - @Override - public String userId() { - return userId; - } - - @Override - public String user() { - return user; - } - - @Override - public String domain() { - return domain; - } - - @Override - public Set roles() { - return roles; - } - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getDomain() { - return domain; - } - - public void setDomain(String domain) { - this.domain = domain; - } - - public ImmutableSet getRoles() { - return roles; - } - - public void setRoles(ImmutableSet roles) { - this.roles = roles; - } - - @Override - public String toString() { - return "clientId:" + clientId + "," + "userId:" + userId + "," + "userName:" + user - + "," + "domain:" + domain + "," + "roles:" + roles ; - } - } -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java deleted file mode 100644 index a954a606..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro.moon; - - -import java.io.IOException; - -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MoonTokenEndpoint extends HttpServlet{ - - private static final long serialVersionUID = 4980356362831585417L; - private static final Logger LOG = LoggerFactory.getLogger(MoonTokenEndpoint.class); - - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { - LOG.debug("MoonTokenEndpoint Servlet doPost"); - } - -} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-shiro/src/main/resources/WEB-INF/web.xml b/odl-aaa-moon/aaa-shiro/src/main/resources/WEB-INF/web.xml deleted file mode 100644 index 63288c23..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/resources/WEB-INF/web.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - MOON - org.opendaylight.aaa.shiro.moon.MoonTokenEndpoint - 1 - - - - MOON - /token - - - MOON - /revoke - - - MOON - /validate - - - MOON - /* - - - - - shiroEnvironmentClass - org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment - - - - org.apache.shiro.web.env.EnvironmentLoaderListener - - - - ShiroFilter - org.opendaylight.aaa.shiro.filters.AAAFilter - - - - ShiroFilter - /* - - \ No newline at end of file diff --git a/odl-aaa-moon/aaa-shiro/src/main/resources/shiro.ini b/odl-aaa-moon/aaa-shiro/src/main/resources/shiro.ini deleted file mode 100644 index d84f9fa0..00000000 --- a/odl-aaa-moon/aaa-shiro/src/main/resources/shiro.ini +++ /dev/null @@ -1,95 +0,0 @@ -# -# Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. -# -# This program and the accompanying materials are made available under the -# terms of the Eclipse Public License v1.0 which accompanies this distribution, -# and is available at http://www.eclipse.org/legal/epl-v10.html -# - -############################################################################### -# shiro.ini # -# # -# Configuration of OpenDaylight's aaa-shiro feature. Provided Realm # -# implementations include: # -# - TokenAuthRealm (enabled by default) # -# - ODLJndiLdapRealm (disabled by default) # -# - ODLJndiLdapRealmAuthNOnly (disabled by default) # -# Basic user configuration through shiro.ini is disabled for security # -# purposes. # -############################################################################### - - - -[main] -############################################################################### -# realms # -# # -# This section is dedicated to setting up realms for OpenDaylight. Realms # -# are essentially different methods for providing AAA. ODL strives to provide# -# highly-configurable AAA by providing pluggable infrastructure. By deafult, # -# TokenAuthRealm is enabled out of the box (which bridges to the existing AAA # -# mechanisms). More than one realm can be enabled, and the realms are # -# tried Round-Robin until: # -# 1) a realm successfully authenticates the incoming request # -# 2) all realms are exhausted, and 401 is returned # -############################################################################### - -# ODL provides a few LDAP implementations, which are disabled out of the box. -# ODLJndiLdapRealm includes authorization functionality based on LDAP elements -# extracted through and LDAP search. This requires a bit of knowledge about -# how your LDAP system is setup. An example is provided below: -#ldapRealm = org.opendaylight.aaa.shiro.realm.ODLJndiLdapRealm -#ldapRealm.userDnTemplate = uid={0},ou=People,dc=DOMAIN,dc=TLD -#ldapRealm.contextFactory.url = ldap://:389 -#ldapRealm.searchBase = dc=DOMAIN,dc=TLD -#ldapRealm.ldapAttributeForComparison = objectClass - -# ODL also provides ODLJndiLdapRealmAuthNOnly. Essentially, this allows -# access through AAAFilter to any user that can authenticate against the -# provided LDAP server. -#ldapRealm = org.opendaylight.aaa.shiro.realm.ODLJndiLdapRealmAuthNOnly -#ldapRealm.userDnTemplate = uid={0},ou=People,dc=DOMAIN,dc=TLD -#ldapRealm.contextFactory.url = ldap://:389 - -# Bridge to existing h2/idmlight/mdsal authentication/authorization mechanisms. -# This realm is enabled by default, and utilizes h2-store by default. -tokenAuthRealm = org.opendaylight.aaa.shiro.realm.TokenAuthRealm -moonAuthRealm = org.opendaylight.aaa.shiro.realm.MoonRealm - -# The CSV list of enabled realms. In order to enable a realm, add it to the -# list below: -securityManager.realms = $moonAuthRealm - - -# adds a custom AuthenticationFilter to support OAuth2 for backwards -# compatibility. To disable OAuth2 access, just comment out the next line -# and authcBasic will default to BasicHttpAuthenticationFilter, a -# Shiro-provided class. -authcBasic = org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter -# OAuth2 Filer for moon token AuthN -rest = org.opendaylight.aaa.shiro.filters.MoonOAuthFilter - - - -[urls] -############################################################################### -# url authorization section # -# # -# This section is dedicated to defining url-based authorization according to: # -# http://shiro.apache.org/web.html # -############################################################################### -#Filtering REST requests with AAAFilter -/v1/users** = authcBasic -/v1/domains** = authcBasic -/v1/roles** = authcBasic - -#Filter OAuth2 request$ -/token = rest - -# General access through AAAFilter requires valid credentials (AuthN only). -/** = authcBasic - -# Access to the credential store is limited to the valid users who have the -# admin role. The following line is only needed if the mdsal store is enabled -#(the mdsal store is disabled by default). -/config/aaa-authn-model** = authcBasic,roles[admin] diff --git a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java deleted file mode 100644 index 2d9c8976..00000000 --- a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.opendaylight.aaa.shiro.filters.AAAFilter; - -/** - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -public class ServiceProxyTest { - - @Test - public void testGetInstance() { - // ensures that singleton pattern is working - assertNotNull(ServiceProxy.getInstance()); - } - - @Test - public void testGetSetEnabled() { - // combines set and get tests. These are important in this instance, - // because getEnabled allows an optional callback Filter. - ServiceProxy.getInstance().setEnabled(true); - assertTrue(ServiceProxy.getInstance().getEnabled(null)); - - AAAFilter testFilter = new AAAFilter(); - // register the filter - ServiceProxy.getInstance().getEnabled(testFilter); - assertTrue(testFilter.isEnabled()); - - ServiceProxy.getInstance().setEnabled(false); - assertFalse(ServiceProxy.getInstance().getEnabled(testFilter)); - assertFalse(testFilter.isEnabled()); - } -} diff --git a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java deleted file mode 100644 index 38658f0c..00000000 --- a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro.authorization; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import com.google.common.collect.Sets; -import java.util.Collection; -import org.junit.Test; - -/** - * A few basic test cases for the DefualtRBACRules singleton container. - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - * - */ -public class DefaultRBACRulesTest { - - @Test - public void testGetInstance() { - assertNotNull(DefaultRBACRules.getInstance()); - assertEquals(DefaultRBACRules.getInstance(), DefaultRBACRules.getInstance()); - } - - @Test - public void testGetRBACRules() { - Collection rbacRules = DefaultRBACRules.getInstance().getRBACRules(); - assertNotNull(rbacRules); - - // check that a copy was returned - int originalSize = rbacRules.size(); - rbacRules.add(RBACRule.createAuthorizationRule("fakeurl/*", Sets.newHashSet("admin"))); - assertEquals(originalSize, DefaultRBACRules.getInstance().getRBACRules().size()); - } - -} diff --git a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java deleted file mode 100644 index 825fe626..00000000 --- a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro.authorization; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import com.google.common.collect.Sets; -import java.util.Collection; -import java.util.HashSet; -import org.junit.Test; - -public class RBACRuleTest { - - private static final String BASIC_RBAC_RULE_URL_PATTERN = "/*"; - private static final Collection BASIC_RBAC_RULE_ROLES = Sets.newHashSet("admin"); - private RBACRule basicRBACRule = RBACRule.createAuthorizationRule(BASIC_RBAC_RULE_URL_PATTERN, - BASIC_RBAC_RULE_ROLES); - - private static final String COMPLEX_RBAC_RULE_URL_PATTERN = "/auth/v1/"; - private static final Collection COMPLEX_RBAC_RULE_ROLES = Sets.newHashSet("admin", - "user"); - private RBACRule complexRBACRule = RBACRule.createAuthorizationRule( - COMPLEX_RBAC_RULE_URL_PATTERN, COMPLEX_RBAC_RULE_ROLES); - - @Test - public void testCreateAuthorizationRule() { - // positive test cases - assertNotNull(RBACRule.createAuthorizationRule(BASIC_RBAC_RULE_URL_PATTERN, - BASIC_RBAC_RULE_ROLES)); - assertNotNull(RBACRule.createAuthorizationRule(COMPLEX_RBAC_RULE_URL_PATTERN, - COMPLEX_RBAC_RULE_ROLES)); - - // negative test cases - // both null - assertNull(RBACRule.createAuthorizationRule(null, null)); - - // url pattern is null - assertNull(RBACRule.createAuthorizationRule(null, BASIC_RBAC_RULE_ROLES)); - // url pattern is empty string - assertNull(RBACRule.createAuthorizationRule("", BASIC_RBAC_RULE_ROLES)); - - // roles is null - assertNull(RBACRule.createAuthorizationRule(BASIC_RBAC_RULE_URL_PATTERN, null)); - // roles is empty collection - assertNull(RBACRule.createAuthorizationRule(COMPLEX_RBAC_RULE_URL_PATTERN, - new HashSet())); - } - - @Test - public void testGetUrlPattern() { - assertEquals(BASIC_RBAC_RULE_URL_PATTERN, basicRBACRule.getUrlPattern()); - assertEquals(COMPLEX_RBAC_RULE_URL_PATTERN, complexRBACRule.getUrlPattern()); - } - - @Test - public void testGetRoles() { - assertTrue(BASIC_RBAC_RULE_ROLES.containsAll(basicRBACRule.getRoles())); - basicRBACRule.getRoles().clear(); - // test that getRoles() produces a new object - assertFalse(basicRBACRule.getRoles().isEmpty()); - assertTrue(basicRBACRule.getRoles().containsAll(BASIC_RBAC_RULE_ROLES)); - - assertTrue(COMPLEX_RBAC_RULE_ROLES.containsAll(complexRBACRule.getRoles())); - complexRBACRule.getRoles().add("newRole"); - // test that getRoles() produces a new object - assertFalse(complexRBACRule.getRoles().contains("newRole")); - assertTrue(complexRBACRule.getRoles().containsAll(COMPLEX_RBAC_RULE_ROLES)); - } - - @Test - public void testGetRolesInShiroFormat() { - final String BASIC_RBAC_RULE_EXPECTED_SHIRO_FORMAT = "roles[admin]"; - assertEquals(BASIC_RBAC_RULE_EXPECTED_SHIRO_FORMAT, basicRBACRule.getRolesInShiroFormat()); - - // set ordering is not predictable, so both formats must be considered - final String COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_1 = "roles[admin, user]"; - final String COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_2 = "roles[user, admin]"; - assertTrue(COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_1.equals(complexRBACRule - .getRolesInShiroFormat()) - || COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_2.equals(complexRBACRule - .getRolesInShiroFormat())); - } - - @Test - public void testToString() { - final String BASIC_RBAC_RULE_EXPECTED_SHIRO_FORMAT = "/*=roles[admin]"; - assertEquals(BASIC_RBAC_RULE_EXPECTED_SHIRO_FORMAT, basicRBACRule.toString()); - - // set ordering is not predictable,s o both formats must be considered - final String COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_1 = "/auth/v1/=roles[admin, user]"; - final String COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_2 = "/auth/v1/=roles[user, admin]"; - assertTrue(COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_1.equals(complexRBACRule.toString()) - || COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_2.equals(complexRBACRule.toString())); - } - -} diff --git a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java deleted file mode 100644 index 22ce203f..00000000 --- a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro.realm; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.Vector; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.BasicAttributes; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.ldap.LdapContext; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.UsernamePasswordToken; -import org.apache.shiro.authz.AuthorizationInfo; -import org.apache.shiro.realm.ldap.LdapContextFactory; -import org.apache.shiro.subject.PrincipalCollection; -import org.junit.Test; - -/** - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -public class ODLJndiLdapRealmTest { - - /** - * throw-away anonymous test class - */ - class TestNamingEnumeration implements NamingEnumeration { - - /** - * state variable - */ - boolean first = true; - - /** - * returned the first time next() or - * nextElement() is called. - */ - SearchResult searchResult = new SearchResult("testuser", null, new BasicAttributes( - "objectClass", "engineering")); - - /** - * returns true the first time, then false for subsequent calls - */ - @Override - public boolean hasMoreElements() { - return first; - } - - /** - * returns searchResult then null for subsequent calls - */ - @Override - public SearchResult nextElement() { - if (first) { - first = false; - return searchResult; - } - return null; - } - - /** - * does nothing because close() doesn't require any special behavior - */ - @Override - public void close() throws NamingException { - } - - /** - * returns true the first time, then false for subsequent calls - */ - @Override - public boolean hasMore() throws NamingException { - return first; - } - - /** - * returns searchResult then null for subsequent calls - */ - @Override - public SearchResult next() throws NamingException { - if (first) { - first = false; - return searchResult; - } - return null; - } - }; - - /** - * throw away test class - * - * @author ryan - */ - class TestPrincipalCollection implements PrincipalCollection { - /** - * - */ - private static final long serialVersionUID = -1236759619455574475L; - - Vector collection = new Vector(); - - public TestPrincipalCollection(String element) { - collection.add(element); - } - - @Override - public Iterator iterator() { - return collection.iterator(); - } - - @Override - public List asList() { - return collection; - } - - @Override - public Set asSet() { - HashSet set = new HashSet(); - set.addAll(collection); - return set; - } - - @Override - public Collection byType(Class arg0) { - return null; - } - - @Override - public Collection fromRealm(String arg0) { - return collection; - } - - @Override - public Object getPrimaryPrincipal() { - return collection.firstElement(); - } - - @Override - public Set getRealmNames() { - return null; - } - - @Override - public boolean isEmpty() { - return collection.isEmpty(); - } - - @Override - public T oneByType(Class arg0) { - // TODO Auto-generated method stub - return null; - } - }; - - @Test - public void testGetUsernameAuthenticationToken() { - AuthenticationToken authenticationToken = null; - assertNull(ODLJndiLdapRealm.getUsername(authenticationToken)); - AuthenticationToken validAuthenticationToken = new UsernamePasswordToken("test", - "testpassword"); - assertEquals("test", ODLJndiLdapRealm.getUsername(validAuthenticationToken)); - } - - @Test - public void testGetUsernamePrincipalCollection() { - PrincipalCollection pc = null; - assertNull(new ODLJndiLdapRealm().getUsername(pc)); - TestPrincipalCollection tpc = new TestPrincipalCollection("testuser"); - String username = new ODLJndiLdapRealm().getUsername(tpc); - assertEquals("testuser", username); - } - - @Test - public void testQueryForAuthorizationInfoPrincipalCollectionLdapContextFactory() - throws NamingException { - LdapContext ldapContext = mock(LdapContext.class); - // emulates an ldap search and returns the mocked up test class - when( - ldapContext.search((String) any(), (String) any(), - (SearchControls) any())).thenReturn(new TestNamingEnumeration()); - LdapContextFactory ldapContextFactory = mock(LdapContextFactory.class); - when(ldapContextFactory.getSystemLdapContext()).thenReturn(ldapContext); - AuthorizationInfo authorizationInfo = new ODLJndiLdapRealm().queryForAuthorizationInfo( - new TestPrincipalCollection("testuser"), ldapContextFactory); - assertNotNull(authorizationInfo); - assertFalse(authorizationInfo.getRoles().isEmpty()); - assertTrue(authorizationInfo.getRoles().contains("engineering")); - } - - @Test - public void testBuildAuthorizationInfo() { - assertNull(ODLJndiLdapRealm.buildAuthorizationInfo(null)); - Set roleNames = new HashSet(); - roleNames.add("engineering"); - AuthorizationInfo authorizationInfo = ODLJndiLdapRealm.buildAuthorizationInfo(roleNames); - assertNotNull(authorizationInfo); - assertFalse(authorizationInfo.getRoles().isEmpty()); - assertTrue(authorizationInfo.getRoles().contains("engineering")); - } - - @Test - public void testGetRoleNamesForUser() throws NamingException { - ODLJndiLdapRealm ldapRealm = new ODLJndiLdapRealm(); - LdapContext ldapContext = mock(LdapContext.class); - - // emulates an ldap search and returns the mocked up test class - when( - ldapContext.search((String) any(), (String) any(), - (SearchControls) any())).thenReturn(new TestNamingEnumeration()); - - // extracts the roles for "testuser" and ensures engineering is returned - Set roles = ldapRealm.getRoleNamesForUser("testuser", ldapContext); - assertFalse(roles.isEmpty()); - assertTrue(roles.iterator().next().equals("engineering")); - } - - @Test - public void testCreateSearchControls() { - SearchControls searchControls = ODLJndiLdapRealm.createSearchControls(); - assertNotNull(searchControls); - int expectedSearchScope = SearchControls.SUBTREE_SCOPE; - int actualSearchScope = searchControls.getSearchScope(); - assertEquals(expectedSearchScope, actualSearchScope); - } - -} diff --git a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java deleted file mode 100644 index f2eb92b5..00000000 --- a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro.realm; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.google.common.collect.Lists; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.shiro.authc.AuthenticationToken; -import org.junit.Test; - -/** - * - * @author Ryan Goulding (ryandgoulding@gmail.com) - * - */ -public class TokenAuthRealmTest extends TokenAuthRealm { - - private TokenAuthRealm testRealm = new TokenAuthRealm(); - - @Test - public void testTokenAuthRealm() { - assertEquals("TokenAuthRealm", testRealm.getName()); - } - - @Test(expected = NullPointerException.class) - public void testDoGetAuthorizationInfoPrincipalCollectionNullCacheToken() { - testRealm.doGetAuthorizationInfo(null); - } - - @Test - public void testGetUsernamePasswordDomainString() { - final String username = "user"; - final String password = "password"; - final String domain = "domain"; - final String expectedUsernamePasswordString = "user:password:domain"; - assertEquals(expectedUsernamePasswordString, getUsernamePasswordDomainString(username, password, domain)); - } - - @Test - public void testGetEncodedToken() { - final String stringToEncode = "admin1:admin1"; - final byte[] bytesToEncode = stringToEncode.getBytes(); - final String expectedToken = org.apache.shiro.codec.Base64.encodeToString(bytesToEncode); - assertEquals(expectedToken, getEncodedToken(stringToEncode)); - } - - @Test - public void testGetTokenAuthHeader() { - final String encodedCredentials = getEncodedToken(getUsernamePasswordDomainString("user1", - "password", "sdn")); - final String expectedTokenAuthHeader = "Basic " + encodedCredentials; - assertEquals(expectedTokenAuthHeader, getTokenAuthHeader(encodedCredentials)); - } - - @Test - public void testFormHeadersWithToken() { - final String authHeader = getEncodedToken(getTokenAuthHeader(getUsernamePasswordDomainString( - "user1", "password", "sdn"))); - final Map> expectedHeaders = new HashMap>(); - expectedHeaders.put("Authorization", Lists.newArrayList(authHeader)); - final Map> actualHeaders = formHeadersWithToken(authHeader); - List value; - for (String key : expectedHeaders.keySet()) { - value = expectedHeaders.get(key); - assertTrue(actualHeaders.get(key).equals(value)); - } - } - - @Test - public void testFormHeaders() { - final String username = "basicUser"; - final String password = "basicPassword"; - final String domain = "basicDomain"; - final String authHeader = getTokenAuthHeader(getEncodedToken(getUsernamePasswordDomainString( - username, password, domain))); - final Map> expectedHeaders = new HashMap>(); - expectedHeaders.put("Authorization", Lists.newArrayList(authHeader)); - final Map> actualHeaders = formHeaders(username, password, domain); - List value; - for (String key : expectedHeaders.keySet()) { - value = expectedHeaders.get(key); - assertTrue(actualHeaders.get(key).equals(value)); - } - } - - @Test - public void testIsTokenAuthAvailable() { - assertFalse(testRealm.isTokenAuthAvailable()); - } - - @Test(expected = org.apache.shiro.authc.AuthenticationException.class) - public void testDoGetAuthenticationInfoAuthenticationToken() { - testRealm.doGetAuthenticationInfo(null); - } - - @Test - public void testExtractUsernameNullUsername() { - AuthenticationToken at = mock(AuthenticationToken.class); - when(at.getPrincipal()).thenReturn(null); - assertNull(extractUsername(at)); - } - - @Test(expected = ClassCastException.class) - public void testExtractPasswordNullPassword() { - AuthenticationToken at = mock(AuthenticationToken.class); - when(at.getPrincipal()).thenReturn("username"); - when(at.getCredentials()).thenReturn(null); - extractPassword(at); - } - - @Test(expected = ClassCastException.class) - public void testExtractUsernameBadUsernameClass() { - AuthenticationToken at = mock(AuthenticationToken.class); - when(at.getPrincipal()).thenReturn(new Integer(1)); - extractUsername(at); - } - - @Test(expected = ClassCastException.class) - public void testExtractPasswordBadPasswordClass() { - AuthenticationToken at = mock(AuthenticationToken.class); - when(at.getPrincipal()).thenReturn("username"); - when(at.getCredentials()).thenReturn(new Integer(1)); - extractPassword(at); - } -} diff --git a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java deleted file mode 100644 index 141d0ce5..00000000 --- a/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.aaa.shiro.web.env; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import org.apache.shiro.config.Ini; -import org.apache.shiro.config.Ini.Section; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * @author Ryan Goulding (ryandgoulding@gmail.com) - */ -public class KarafIniWebEnvironmentTest { - private static File iniFile; - - @BeforeClass - public static void setup() throws IOException { - iniFile = createShiroIniFile(); - assertTrue(iniFile.exists()); - } - - @AfterClass - public static void teardown() { - iniFile.delete(); - } - - private static String createFakeShiroIniContents() { - return "[users]\n" + "admin=admin, ROLE_ADMIN \n" + "[roles]\n" + "ROLE_ADMIN = *\n" - + "[urls]\n" + "/** = authcBasic"; - } - - private static File createShiroIniFile() throws IOException { - File shiroIni = File.createTempFile("shiro", "ini"); - FileWriter writer = new FileWriter(shiroIni); - writer.write(createFakeShiroIniContents()); - writer.flush(); - writer.close(); - return shiroIni; - } - - @Test - public void testCreateShiroIni() throws IOException { - Ini ini = KarafIniWebEnvironment.createShiroIni(iniFile.getAbsolutePath()); - assertNotNull(ini); - assertNotNull(ini.getSection("users")); - assertNotNull(ini.getSection("roles")); - assertNotNull(ini.getSection("urls")); - Section usersSection = ini.getSection("users"); - assertTrue(usersSection.containsKey("admin")); - assertTrue(usersSection.get("admin").contains("admin")); - assertTrue(usersSection.get("admin").contains("ROLE_ADMIN")); - } - - @Test - public void testCreateFileBasedIniPath() { - String testPath = "/shiro.ini"; - String expectedFileBasedIniPath = KarafIniWebEnvironment.SHIRO_FILE_PREFIX + testPath; - String actualFileBasedIniPath = KarafIniWebEnvironment.createFileBasedIniPath(testPath); - assertEquals(expectedFileBasedIniPath, actualFileBasedIniPath); - } - -} diff --git a/odl-aaa-moon/artifacts/pom.xml b/odl-aaa-moon/artifacts/pom.xml deleted file mode 100644 index 8efad137..00000000 --- a/odl-aaa-moon/artifacts/pom.xml +++ /dev/null @@ -1,231 +0,0 @@ - - - - - 4.0.0 - - - org.opendaylight.odlparent - odlparent-lite - 1.6.1-Beryllium-SR1 - - - - org.opendaylight.aaa - aaa-artifacts - 0.3.1-Beryllium-SR1 - pom - - - - - ${project.groupId} - aaa-authn - ${project.version} - - - ${project.groupId} - aaa-authn - ${project.version} - cfg - config - - - ${project.groupId} - aaa-authn-api - ${project.version} - - - ${project.groupId} - aaa-authn-basic - ${project.version} - - - ${project.groupId} - aaa-authn-federation - ${project.version} - - - ${project.groupId} - aaa-authn-federation - ${project.version} - cfg - config - - - ${project.groupId} - aaa-authn-keystone - ${project.version} - - - - ${project.groupId} - aaa-authn-mdsal-api - ${project.version} - - - ${project.groupId} - aaa-authn-mdsal-store-impl - ${project.version} - - - ${project.groupId} - aaa-authn-mdsal-config - ${project.version} - xml - config - - - ${project.groupId} - aaa-shiro - ${project.version} - - - ${project.groupId} - aaa-shiro-act - ${project.version} - - - ${project.groupId} - aaa-authn-sssd - ${project.version} - - - ${project.groupId} - aaa-authn-store - ${project.version} - - - ${project.groupId} - aaa-authn-store - ${project.version} - cfg - config - - - ${project.groupId} - aaa-authn-sts - ${project.version} - - - - ${project.groupId} - aaa-authz-model - ${project.version} - - - ${project.groupId} - aaa-authz-service - ${project.version} - - - ${project.groupId} - authz-service-config - ${project.version} - xml - config - - - ${project.groupId} - authz-restconf-config - ${project.version} - xml - config - - - - ${project.groupId} - aaa-credential-store-api - ${project.version} - - - ${project.groupId} - aaa-idmlight - ${project.version} - - - ${project.groupId} - aaa-idmlight - ${project.version} - xml - config - - - ${project.groupId} - aaa-authn-idpmapping - ${project.version} - - - - ${project.groupId} - features-aaa-api - ${project.version} - features - xml - - - ${project.groupId} - features-aaa-authn - ${project.version} - features - xml - - - ${project.groupId} - features-aaa-authz - ${project.version} - features - xml - - - ${project.groupId} - aaa-h2-store - ${project.version} - - - ${project.groupId} - aaa-h2-store - ${project.version} - config - xml - - - ${project.groupId} - features-aaa-shiro - ${project.version} - features - xml - - - ${project.groupId} - features-aaa - ${project.version} - features - xml - - - - - - http://nexus.opendaylight.org/content - - - - - - opendaylight-release - ${nexusproxy}/repositories/opendaylight.release/ - - - - opendaylight-snapshot - ${nexusproxy}/repositories/opendaylight.snapshot/ - - - diff --git a/odl-aaa-moon/commons/docs/AuthNusecases.vsd b/odl-aaa-moon/commons/docs/AuthNusecases.vsd deleted file mode 100644 index ddd59fb3f05301f2a2f652757404ccb089e3fd4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 206336 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�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%0D6T_>Ysn;e#FOGgoZPps=plmVa~;Yi0) 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 Jtx5$J{(mDnHedh% diff --git a/odl-aaa-moon/commons/docs/federated_authn1.png b/odl-aaa-moon/commons/docs/federated_authn1.png deleted file mode 100644 index 199f6f4d691a4227248d5f9a4d946a51ffeb6237..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36542 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 (FreeIPA | - | ------------------- | | Kerberos And | - | ______________________| | LDAP) | - ------------------ -Figure 1: Shows the setup for a simple Federated AAA use case utilizing -FreeIPA as an identity provider. - - -These instructions were written for Fedora 20, since SSSD is unique to RHEL based -distributions. SSSD is NOT a requirement for Federation though; you can use -any supported linux flavor. At this time, SSSD is the only Filter available -with regards to capturing IdP attributes that can be used in making advanced mapping -decisions (such as IdP group membership information). - - - -1) Install FreeIPA Server on ipa.example.com. This is achieved through running: -# yum install freeipa-server bind bind-dyndb-ldap -# ipa-server-intall - - - -2) Add a FreeIPA user called testuser: -$ kinit admin@EXAMPLE.COM -$ ipa group-add odl_users --desc "ODL Users" -$ ipa group-add odl_admin --desc "ODL Admin" -$ ipa user-add testuser --first Test --last USER --email test.user@example.com -$ ipa group-add-member odl_users --user testuser -$ ipa group-add-member odl_admin --user testuser - - - -3) Install FreeIPA Client on odl.example.com. This is achieved through running: -# yum install freeipa-client -# ipa-client-install - - - -4) Set up Client keytab for HTTP access on odl.example.com: -# ipa-getkeytab -p HTTP/odl.brcd-sssd-tb.com@BRCD-SSSD-TB.COM \ - -s freeipa.brcd-sssd-tb.com -k /etc/krb5.keytab -# chmod 644 /etc/krb5.keytab -NOTE: The second command allows Apache to read the keytab. There are more -secure methods to support such access through SELINUX, but they are outside -the scope of this tutorial. - - - -5) Install Apache on odl.example.com. This is achieved through running: -# yum install httpd - - - -6) Create an Apache application to broker federation between ODL and FreeIPA. -Create the following file on odl.example.com: - -[root@odl /]# cat /etc/httpd/conf.d/my_app.conf - - AuthType Kerberos - AuthName "Kerberos Login" - KrbMethodNegotiate On - KrbMethodK5Passwd on - KrbAuthRealms EXAMPLE.COM - Krb5KeyTab /etc/krb5.keytab - require valid-user - - - - - - RequestHeader set X-SSSD-REMOTE_USER expr=%{REMOTE_USER} - RequestHeader set X-SSSD-AUTH_TYPE expr=%{AUTH_TYPE} - RequestHeader set X-SSSD-REMOTE_HOST expr=%{REMOTE_HOST} - RequestHeader set X-SSSD-REMOTE_ADDR expr=%{REMOTE_ADDR} - LookupUserAttr mail REMOTE_USER_EMAIL - RequestHeader set X-SSSD-REMOTE_USER_EMAIL %{REMOTE_USER_EMAIL}e - LookupUserAttr givenname REMOTE_USER_FIRSTNAME - RequestHeader set X-SSSD-REMOTE_USER_FIRSTNAME %{REMOTE_USER_FIRSTNAME}e - LookupUserAttr sn REMOTE_USER_LASTNAME - RequestHeader set X-SSSD-REMOTE_USER_LASTNAME %{REMOTE_USER_LASTNAME}e - LookupUserGroups REMOTE_USER_GROUPS ":" - RequestHeader set X-SSSD-REMOTE_USER_GROUPS %{REMOTE_USER_GROUPS}e - - -ProxyPass / http://localhost:8383/ -ProxyPassReverse / http://localhost:8383/ - - - -7) Install the ODL distribution in the /opt folder on odl.example.com. - - - -8) Add a federation connector to the jetty server hosting ODL on -odl.example.com: - -[user@odl distribution]$ cat etc/jetty.xml - - - - - - - - - - - - - - - - - - - - - - 300000 - 2 - false - 8443 - 20000 - 5000 - - - - - - - - 127.0.0.1 - 8383 - 300000 - 2 - false - 8445 - federationConn - 20000 - 5000 - - - - - - - - - - - - - - karaf - karaf - - - org.apache.karaf.jaas.boot.principal.RolePrincipal - - - - - - - - - - default - karaf - - - org.apache.karaf.jaas.boot.principal.RolePrincipal - - - - - - - - - - -9) Add the idp_mapping rules file on odl.example.com - -[user@odl distribution]$ cat etc/idp_mapping_rules.json -[ - { - "mapping":{ - "ClientId":"1", - "UserId":"1", - "User":"admin", - "Domain":"BRCD-SSSD-TB.COM", - "roles":"$roles" - }, - "statement_blocks":[ - [ - [ - "set", - "$groups", - [ - - ] - ], - [ - "set", - "$roles", - [ - "admin", - "user" - ] - ] - ] - ] - } -] - -NOTE: This is a very basic mapping example in which all federated users are -mapped into the default "admin" account. - - - -10) Start ODL and install the following features on odl.example.com: -# bin/karaf -karaf> feature:install odl-aaa-authn-sssd-no-cluster odl-restconf - - - -11) Get a refresh_token on odl.example.com through Apache proxy port (80 forwarded to 8383): -[user@odl distribution]$ kinit testuser -[user@odl distribution]$ curl -s --negotiate -u : -X POST http://odl.example.com/oauth2/federation/ - - - -12) Obtain an access_token on odl.example.com through normal port (8181): -[user@odl distribution]$ curl -s -d 'grant_type=refresh_token&refresh_token=&scope=sdn' http://odl.example.com:8181/oauth2/token - - - -13) Use the access_token to make authenticated rest calls from odl.example.com through normal port (8181): -[user@odl distribution]$ curl -s -H 'Authorization: Bearer ' http://odl.brcd-sssd-tb.com:8181/restconf/streams/ - diff --git a/odl-aaa-moon/commons/federation/idp_mapping_rules.json.example b/odl-aaa-moon/commons/federation/idp_mapping_rules.json.example deleted file mode 100644 index 98bacb0a..00000000 --- a/odl-aaa-moon/commons/federation/idp_mapping_rules.json.example +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "mapping":{ - "ClientId":"1", - "UserId":"1", - "User":"admin", - "Domain":"BRCD-SSSD-TB.COM", - "roles":"$roles" - }, - "statement_blocks":[ - [ - [ - "set", - "$groups", - [ - - ] - ], - [ - "set", - "$roles", - [ - "admin", - "user" - ] - ] - ] - ] - } -] diff --git a/odl-aaa-moon/commons/federation/jetty.xml.example b/odl-aaa-moon/commons/federation/jetty.xml.example deleted file mode 100644 index c4cb2a7d..00000000 --- a/odl-aaa-moon/commons/federation/jetty.xml.example +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - 300000 - 2 - false - 8443 - 20000 - 5000 - - - - - - - - 127.0.0.1 - 8383 - 300000 - 2 - false - 8445 - federationConn - 20000 - 5000 - - - - - - - - - - - - - - karaf - karaf - - - org.apache.karaf.jaas.boot.principal.RolePrincipal - - - - - - - - - - default - karaf - - - org.apache.karaf.jaas.boot.principal.RolePrincipal - - - - - - - - diff --git a/odl-aaa-moon/commons/federation/my_app.conf.example b/odl-aaa-moon/commons/federation/my_app.conf.example deleted file mode 100644 index 71c8ad87..00000000 --- a/odl-aaa-moon/commons/federation/my_app.conf.example +++ /dev/null @@ -1,31 +0,0 @@ -LoadModule lookup_identity_module modules/mod_lookup_identity.so - - - AuthType Kerberos - AuthName "Kerberos Login" - KrbMethodNegotiate On - KrbMethodK5Passwd on - KrbAuthRealms EXAMPLE.COM - Krb5KeyTab /etc/krb5.keytab - require valid-user - - - - - - RequestHeader set X-SSSD-REMOTE_USER expr=%{REMOTE_USER} - RequestHeader set X-SSSD-AUTH_TYPE expr=%{AUTH_TYPE} - RequestHeader set X-SSSD-REMOTE_HOST expr=%{REMOTE_HOST} - RequestHeader set X-SSSD-REMOTE_ADDR expr=%{REMOTE_ADDR} - LookupUserAttr mail REMOTE_USER_EMAIL - RequestHeader set X-SSSD-REMOTE_USER_EMAIL %{REMOTE_USER_EMAIL}e - LookupUserAttr givenname REMOTE_USER_FIRSTNAME - RequestHeader set X-SSSD-REMOTE_USER_FIRSTNAME %{REMOTE_USER_FIRSTNAME}e - LookupUserAttr sn REMOTE_USER_LASTNAME - RequestHeader set X-SSSD-REMOTE_USER_LASTNAME %{REMOTE_USER_LASTNAME}e - LookupUserGroups REMOTE_USER_GROUPS ":" - RequestHeader set X-SSSD-REMOTE_USER_GROUPS %{REMOTE_USER_GROUPS}e - - -ProxyPass / http://localhost:8383/ -ProxyPassReverse / http://localhost:8383/ diff --git a/odl-aaa-moon/commons/postman_examples/AAA_AuthZ_MDSAL.json.postman_collection b/odl-aaa-moon/commons/postman_examples/AAA_AuthZ_MDSAL.json.postman_collection deleted file mode 100644 index 15193a70..00000000 --- a/odl-aaa-moon/commons/postman_examples/AAA_AuthZ_MDSAL.json.postman_collection +++ /dev/null @@ -1,77 +0,0 @@ -{ - "id": "273974a1-2df8-b0a6-57f9-1397cd1628d7", - "name": "AAA AuthZ MDSAL", - "description": "This Postman collection contains some of the common operations that are necessary to \"provision\" authorization services on top of ODL.", - "order": [ - "7959a1f4-703a-417a-9d4c-70ab56c0e57f", - "262c9b05-04a6-8dfa-5eb3-c9f9f90b3c4a", - "4df58109-fd50-dbdf-b982-7e59d3475544" - ], - "folders": [], - "timestamp": 1439405060911, - "owner": 0, - "remoteLink": "", - "public": false, - "requests": [ - { - "id": "262c9b05-04a6-8dfa-5eb3-c9f9f90b3c4a", - "headers": "Authorization: Basic YWRtaW46YWRtaW4=\n", - "url": "http://localhost:8181/restconf/config/authorization-schema:simple-authorization/policies/RestConfService/", - "pathVariables": {}, - "preRequestScript": "", - "method": "GET", - "collectionId": "273974a1-2df8-b0a6-57f9-1397cd1628d7", - "data": [], - "dataMode": "raw", - "name": "Get configuration authorization schema with admin role", - "description": "", - "descriptionFormat": "html", - "time": 1439405954342, - "version": 2, - "responses": [], - "tests": "", - "currentHelper": "normal", - "helperAttributes": {}, - "rawModeData": "" - }, - { - "id": "4df58109-fd50-dbdf-b982-7e59d3475544", - "headers": "Authorization: Basic dXNlcjp1c2Vy\n", - "url": "http://localhost:8181/restconf/config/authorization-schema:simple-authorization/policies/RestConfService/", - "preRequestScript": "", - "pathVariables": {}, - "method": "GET", - "data": [], - "dataMode": "params", - "version": 2, - "tests": "", - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1439406616859, - "name": "Get configuration authorization schema with user role", - "description": "", - "collectionId": "273974a1-2df8-b0a6-57f9-1397cd1628d7", - "responses": [] - }, - { - "id": "7959a1f4-703a-417a-9d4c-70ab56c0e57f", - "headers": "Authorization: Basic YWRtaW46YWRtaW4=\nContent-Type: application/json\n", - "url": "http://localhost:8181/restconf/config/authorization-schema:simple-authorization/policies/RestConfService/", - "preRequestScript": "", - "pathVariables": {}, - "method": "PUT", - "data": [], - "dataMode": "raw", - "version": 2, - "tests": "", - "currentHelper": "normal", - "helperAttributes": {}, - "time": 1439405844861, - "name": "Secure RestConfService for admin role", - "description": "", - "collectionId": "273974a1-2df8-b0a6-57f9-1397cd1628d7", - "responses": [], - "rawModeData": "{\n \"policies\": {\n \"resource\": \"*\",\n \"service\":\"RestConfService\",\n \"role\": \"admin\"\n }\n}" - } - ] -} \ No newline at end of file diff --git a/odl-aaa-moon/distribution-karaf/pom.xml b/odl-aaa-moon/distribution-karaf/pom.xml deleted file mode 100644 index dc65d84f..00000000 --- a/odl-aaa-moon/distribution-karaf/pom.xml +++ /dev/null @@ -1,274 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - - distribution-karaf - pom - - 3.0 - - - - - - org.apache.karaf.features - framework - ${karaf.version} - kar - - - org.apache.karaf.features - standard - ${karaf.version} - features - xml - runtime - - - - - org.opendaylight.controller - karaf.branding - ${karaf.branding.version} - compile - - - - - org.opendaylight.controller - opendaylight-karaf-resources - ${karaf.resources.version} - - - - - org.opendaylight.aaa - features-aaa-api - features - ${project.version} - xml - runtime - - - org.opendaylight.aaa - features-aaa - features - ${project.version} - xml - runtime - - - org.opendaylight.aaa - features-aaa-authz - features - ${project.version} - xml - runtime - - - org.opendaylight.aaa - features-aaa-shiro - features - ${project.version} - xml - runtime - - - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.apache.felix - maven-bundle-plugin - [0,) - - cleanVersions - - - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - [0,) - - copy - unpack - - - - - - - - - org.apache.karaf.tooling - karaf-maven-plugin - [0,) - - commands-generate-help - - - - - - - - - org.fusesource.scalate - maven-scalate-plugin - [0,) - - sitegen - - - - - - - - - org.apache.servicemix.tooling - depends-maven-plugin - [0,) - - generate-depends-file - - - - - - - - - - - - - - - org.apache.karaf.tooling - karaf-maven-plugin - true - - - standard - - - - - - - process-resources - - install-kars - - process-resources - - - package - - instance-create-archive - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.6 - - - copy - - copy - - generate-resources - - - - org.opendaylight.controller - karaf.branding - ${karaf.branding.version} - target/assembly/lib - karaf.branding-${karaf.branding.version}.jar - - - - - - unpack-karaf-resources - - unpack-dependencies - - prepare-package - - ${project.build.directory}/assembly - org.opendaylight.controller - opendaylight-karaf-resources - META-INF\/** - true - false - - - - - - org.apache.maven.plugins - maven-antrun-plugin - - - prepare-package - - run - - - - - - - - - - - - - - - - - - - - - - - scm:git:ssh://git.opendaylight.org:29418/aaa.git - scm:git:ssh://git.opendaylight.org:29418/aaa.git - HEAD - https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary - - diff --git a/odl-aaa-moon/features/api/pom.xml b/odl-aaa-moon/features/api/pom.xml deleted file mode 100644 index b64242ae..00000000 --- a/odl-aaa-moon/features/api/pom.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - 4.0.0 - - org.opendaylight.odlparent - features-parent - 1.6.1-Beryllium-SR1 - - - - org.opendaylight.aaa - features-aaa-api - 0.3.1-Beryllium-SR1 - jar - - - 0.8.1-Beryllium-SR1 - 2.0.1-Beryllium-SR1 - - - - - - - org.opendaylight.aaa - aaa-artifacts - ${project.version} - import - pom - - - - - org.opendaylight.yangtools - yangtools-artifacts - ${yangtools.version} - import - pom - - - - - - - org.slf4j - slf4j-api - - - org.slf4j - slf4j-simple - - - com.sun.jersey - jersey-server - provided - - - org.opendaylight.aaa - aaa-authn-api - - - org.opendaylight.aaa - aaa-credential-store-api - - - org.opendaylight.yangtools - features-yangtools - features - xml - - - org.opendaylight.mdsal - features-mdsal - 2.0.1-Beryllium-SR1 - features - xml - - - - - scm:git:ssh://git.opendaylight.org:29418/aaa.git - scm:git:ssh://git.opendaylight.org:29418/aaa.git - HEAD - https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary - - diff --git a/odl-aaa-moon/features/api/src/main/features/features.xml b/odl-aaa-moon/features/api/src/main/features/features.xml deleted file mode 100644 index c526e174..00000000 --- a/odl-aaa-moon/features/api/src/main/features/features.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - mvn:org.opendaylight.yangtools/features-yangtools/{{VERSION}}/xml/features - mvn:org.opendaylight.mdsal/features-mdsal/{{VERSION}}/xml/features - - mvn:com.sun.jersey/jersey-server/{{VERSION}} - mvn:com.sun.jersey/jersey-core/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-api/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-credential-store-api/{{VERSION}} - odl-yangtools-common - odl-mdsal-binding-base - - diff --git a/odl-aaa-moon/features/authn/pom.xml b/odl-aaa-moon/features/authn/pom.xml deleted file mode 100644 index d7d0def8..00000000 --- a/odl-aaa-moon/features/authn/pom.xml +++ /dev/null @@ -1,304 +0,0 @@ - - - - 4.0.0 - - org.opendaylight.odlparent - features-parent - 1.6.1-Beryllium-SR1 - - - - org.opendaylight.aaa - features-aaa - 0.3.1-Beryllium-SR1 - jar - - - 0.4.1-Beryllium-SR1 - 2.0.1-Beryllium-SR1 - 1.3.1-Beryllium-SR1 - 0.8.1-Beryllium-SR1 - 0.3.1-Beryllium-SR1 - - - - - - - org.opendaylight.aaa - aaa-parent - ${project.version} - import - pom - - - - - - - - org.opendaylight.aaa - aaa-shiro - ${shiro.version} - - - org.opendaylight.aaa - aaa-shiro-act - ${shiro.version} - - - org.apache.shiro - shiro-core - - - org.apache.shiro - shiro-web - - - - com.sun.jersey - jersey-servlet - - - com.sun.jersey - jersey-core - - - com.sun.jersey - jersey-server - - - - com.sun.jersey - jersey-client - - - com.sun.jersey - jersey-json - - - org.apache.commons - commons-lang3 - - - org.apache.felix - org.apache.felix.dependencymanager - - - org.apache.felix - org.apache.felix.metatype - - - net.sf.ehcache - ehcache - - - org.apache.geronimo.specs - geronimo-jta_1.1_spec - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.common - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.resourceserver - - - commons-codec - commons-codec - - - org.json - json - - - org.glassfish - javax.json - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.datatype - jackson-datatype-json-org - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-base - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-json-provider - - - com.fasterxml.jackson.module - jackson-module-jaxb-annotations - - - com.google.guava - guava - - - com.h2database - h2 - - - org.opendaylight.aaa - features-aaa-api - features - xml - - - org.opendaylight.aaa - aaa-authn - - - org.opendaylight.aaa - aaa-authn-sts - - - org.opendaylight.aaa - aaa-authn-store - - - org.opendaylight.aaa - aaa-authn-basic - - - org.opendaylight.aaa - aaa-idmlight - - - org.opendaylight.aaa - aaa-idmlight - xml - config - - - org.opendaylight.aaa - aaa-idmlight - ${project.version} - py - config - - - org.opendaylight.aaa - aaa-authn-federation - - - org.opendaylight.aaa - aaa-authn-mdsal-config - xml - config - - - org.opendaylight.aaa - aaa-authn - cfg - config - - - org.opendaylight.aaa - aaa-authn-store - cfg - config - - - org.opendaylight.aaa - aaa-authn-federation - cfg - config - - - org.opendaylight.aaa - aaa-h2-store - - - org.opendaylight.aaa - aaa-h2-store - xml - config - - - - - org.osgi - org.osgi.enterprise - 4.2.0 - - - - - - org.opendaylight.aaa - aaa-authn-mdsal-store-impl - - - org.opendaylight.aaa - aaa-authn-mdsal-api - - - org.opendaylight.yangtools - features-yangtools - features - xml - - - org.opendaylight.controller - features-mdsal - features - xml - - - org.opendaylight.controller - features-config - features - xml - - - org.opendaylight.controller - sal-common-impl - - - - - org.opendaylight.aaa - aaa-authn-sssd - - - - org.opendaylight.aaa - aaa-authn-idpmapping - - - - org.opendaylight.aaa - aaa-authn-keystone - - - - scm:git:ssh://git.opendaylight.org:29418/aaa.git - scm:git:ssh://git.opendaylight.org:29418/aaa.git - HEAD - https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary - - diff --git a/odl-aaa-moon/features/authn/src/main/features/features.xml b/odl-aaa-moon/features/authn/src/main/features/features.xml deleted file mode 100644 index 2c48d2c1..00000000 --- a/odl-aaa-moon/features/authn/src/main/features/features.xml +++ /dev/null @@ -1,247 +0,0 @@ - - - - - mvn:org.opendaylight.aaa/features-aaa-api/{{VERSION}}/xml/features - mvn:org.opendaylight.yangtools/features-yangtools/{{VERSION}}/xml/features - mvn:org.opendaylight.controller/features-config/{{VERSION}}/xml/features - mvn:org.opendaylight.mdsal/features-mdsal/{{VERSION}}/xml/features - mvn:org.opendaylight.controller/features-mdsal/{{VERSION}}/xml/features - - - odl-aaa-api - - - odl-yangtools-common - odl-mdsal-binding-base - odl-mdsal-broker - odl-config-core - - - war - mvn:com.sun.jersey/jersey-servlet/{{VERSION}} - mvn:com.sun.jersey/jersey-core/{{VERSION}} - mvn:com.sun.jersey/jersey-server/{{VERSION}} - - - mvn:org.apache.felix/org.apache.felix.dependencymanager/{{VERSION}} - mvn:org.apache.felix/org.apache.felix.metatype/{{VERSION}} - - - mvn:net.sf.ehcache/ehcache/{{VERSION}} - mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/{{VERSION}} - - - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/{{VERSION}} - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.authzserver/{{VERSION}} - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.resourceserver/{{VERSION}} - mvn:commons-codec/commons-codec/{{VERSION}} - wrap:mvn:org.json/json/{{VERSION}} - - - wrap:mvn:org.apache.commons/commons-lang3/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-shiro-act/{{VERSION}} - mvn:org.apache.shiro/shiro-core/{{VERSION}} - mvn:org.apache.shiro/shiro-web/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-sts/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-store/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-basic/{{VERSION}} - mvn:com.google.guava/guava/{{VERSION}} - - - mvn:org.osgi/org.osgi.enterprise/4.2.0 - wrap:mvn:com.h2database/h2/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-h2-store/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-h2-store/{{VERSION}}/xml/config - - - mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/xml/config - mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/py/config - - mvn:com.fasterxml.jackson.core/jackson-core/{{VERSION}} - mvn:com.fasterxml.jackson.core/jackson-annotations/{{VERSION}} - mvn:com.fasterxml.jackson.core/jackson-databind/{{VERSION}} - mvn:com.fasterxml.jackson.datatype/jackson-datatype-json-org/{{VERSION}} - mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/{{VERSION}} - mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/{{VERSION}} - mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-idpmapping/{{VERSION}} - mvn:org.glassfish/javax.json/{{VERSION}} - - mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}/cfg/config - mvn:org.opendaylight.aaa/aaa-authn-store/{{VERSION}}/cfg/config - mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}/cfg/config - - - - odl-aaa-api - - - odl-yangtools-common - odl-mdsal-binding-base - odl-mdsal-broker - odl-config-core - - - war - mvn:com.sun.jersey/jersey-servlet/{{VERSION}} - mvn:com.sun.jersey/jersey-core/{{VERSION}} - mvn:com.sun.jersey/jersey-server/{{VERSION}} - mvn:com.sun.jersey/jersey-client/${jersey.version} - - - mvn:org.apache.felix/org.apache.felix.dependencymanager/{{VERSION}} - mvn:org.apache.felix/org.apache.felix.metatype/{{VERSION}} - - - mvn:net.sf.ehcache/ehcache/{{VERSION}} - mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-shiro-act/{{VERSION}} - mvn:org.apache.shiro/shiro-core/{{VERSION}} - mvn:org.apache.shiro/shiro-web/{{VERSION}} - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/{{VERSION}} - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.authzserver/{{VERSION}} - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.resourceserver/{{VERSION}} - mvn:commons-codec/commons-codec/{{VERSION}} - wrap:mvn:org.json/json/{{VERSION}} - - - wrap:mvn:org.apache.commons/commons-lang3/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-sts/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-store/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-basic/{{VERSION}} - mvn:com.google.guava/guava/{{VERSION}} - - - mvn:org.osgi/org.osgi.enterprise/4.2.0 - wrap:mvn:com.h2database/h2/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-h2-store/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-h2-store/{{VERSION}}/xml/config - - - mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/xml/config - mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/py/config - - mvn:com.fasterxml.jackson.core/jackson-core/{{VERSION}} - mvn:com.fasterxml.jackson.core/jackson-annotations/{{VERSION}} - mvn:com.fasterxml.jackson.core/jackson-databind/{{VERSION}} - mvn:com.fasterxml.jackson.datatype/jackson-datatype-json-org/{{VERSION}} - mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/{{VERSION}} - mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/{{VERSION}} - mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-idpmapping/{{VERSION}} - mvn:org.glassfish/javax.json/{{VERSION}} - - mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}/cfg/config - mvn:org.opendaylight.aaa/aaa-authn-store/{{VERSION}}/cfg/config - mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}/cfg/config - - - - - - odl-yangtools-common - odl-mdsal-binding-base - odl-mdsal-broker - odl-config-core - - - - mvn:org.apache.felix/org.apache.felix.dependencymanager/{{VERSION}} - mvn:org.apache.felix/org.apache.felix.metatype/{{VERSION}} - - - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/{{VERSION}} - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.authzserver/{{VERSION}} - mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.resourceserver/{{VERSION}} - mvn:commons-codec/commons-codec/1.8 - wrap:mvn:org.json/json/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-shiro-act/{{VERSION}} - mvn:org.apache.shiro/shiro-core/{{VERSION}} - mvn:org.apache.shiro/shiro-web/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-api/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-sts/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-mdsal-api/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-mdsal-store-impl/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-basic/{{VERSION}} - mvn:com.google.guava/guava/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/xml/config - mvn:com.fasterxml.jackson.core/jackson-core/{{VERSION}} - mvn:com.fasterxml.jackson.core/jackson-annotations/{{VERSION}} - mvn:com.fasterxml.jackson.core/jackson-databind/{{VERSION}} - mvn:com.fasterxml.jackson.datatype/jackson-datatype-json-org/{{VERSION}} - mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/{{VERSION}} - mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/{{VERSION}} - mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/{{VERSION}} - wrap:mvn:com.h2database/h2/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-idpmapping/{{VERSION}} - mvn:org.glassfish/javax.json/1.0.4 - - - war - mvn:com.sun.jersey/jersey-servlet/{{VERSION}} - mvn:com.sun.jersey/jersey-core/{{VERSION}} - mvn:com.sun.jersey/jersey-server/{{VERSION}} - - mvn:org.opendaylight.aaa/aaa-authn-mdsal-config/{{VERSION}}/xml/config - mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}/cfg/config - mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}/cfg/config - - - - - odl-aaa-authn - mvn:org.apache.httpcomponents/httpclient-osgi/{{VERSION}} - mvn:org.apache.httpcomponents/httpcore-osgi/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authn-keystone/{{VERSION}} - - - - odl-aaa-authn - mvn:org.opendaylight.aaa/aaa-authn-sssd/{{VERSION}} - - - - odl-aaa-authn-no-cluster - mvn:org.opendaylight.aaa/aaa-authn-sssd/{{VERSION}} - - diff --git a/odl-aaa-moon/features/authz/pom.xml b/odl-aaa-moon/features/authz/pom.xml deleted file mode 100644 index 41808378..00000000 --- a/odl-aaa-moon/features/authz/pom.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - 4.0.0 - - org.opendaylight.odlparent - features-parent - 1.6.1-Beryllium-SR1 - - - - org.opendaylight.aaa - features-aaa-authz - 0.3.1-Beryllium-SR1 - jar - - - 0.4.1-Beryllium-SR1 - 2.0.1-Beryllium-SR1 - 1.3.1-Beryllium-SR1 - 0.8.1-Beryllium-SR1 - - - - - - - org.opendaylight.aaa - aaa-parent - ${project.version} - import - pom - - - - - - - org.opendaylight.aaa - features-aaa-api - features - xml - - - - org.opendaylight.yangtools - features-yangtools - features - xml - - - org.opendaylight.mdsal - features-mdsal - features - ${mdsal.version} - xml - - - org.opendaylight.controller - features-config - features - xml - - - org.opendaylight.controller - features-mdsal - features - xml - - - org.opendaylight.aaa - authz-restconf-config - xml - config - - - org.opendaylight.aaa - aaa-authz-model - - - org.opendaylight.aaa - aaa-authz-service - - - org.opendaylight.aaa - authz-service-config - xml - config - - - - scm:git:ssh://git.opendaylight.org:29418/aaa.git - scm:git:ssh://git.opendaylight.org:29418/aaa.git - HEAD - https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary - - diff --git a/odl-aaa-moon/features/authz/src/main/features/features.xml b/odl-aaa-moon/features/authz/src/main/features/features.xml deleted file mode 100644 index c5239045..00000000 --- a/odl-aaa-moon/features/authz/src/main/features/features.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - mvn:org.opendaylight.yangtools/features-yangtools/{{VERSION}}/xml/features - mvn:org.opendaylight.controller/features-config/{{VERSION}}/xml/features - mvn:org.opendaylight.mdsal/features-mdsal/{{VERSION}}/xml/features - mvn:org.opendaylight.controller/features-mdsal/{{VERSION}}/xml/features - mvn:org.opendaylight.aaa/features-aaa-api/{{VERSION}}/xml/features - - - odl-aaa-api - odl-yangtools-common - odl-mdsal-binding-base - odl-mdsal-broker - odl-config-core - mvn:org.opendaylight.aaa/aaa-authz-model/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-authz-service/{{VERSION}} - mvn:org.opendaylight.aaa/authz-service-config/{{VERSION}}/xml/config - mvn:org.opendaylight.aaa/authz-restconf-config/{{VERSION}}/xml/config - - - diff --git a/odl-aaa-moon/features/pom.xml b/odl-aaa-moon/features/pom.xml deleted file mode 100644 index 261f73c4..00000000 --- a/odl-aaa-moon/features/pom.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - ../parent - - org.opendaylight.aaa - features-aggregator - pom - - shiro - api - authn - authz - - diff --git a/odl-aaa-moon/features/shiro/pom.xml b/odl-aaa-moon/features/shiro/pom.xml deleted file mode 100644 index 50a9971e..00000000 --- a/odl-aaa-moon/features/shiro/pom.xml +++ /dev/null @@ -1,179 +0,0 @@ - - - - 4.0.0 - - org.opendaylight.odlparent - features-parent - 1.6.1-Beryllium-SR1 - - - - org.opendaylight.aaa - features-aaa-shiro - 0.3.1-Beryllium-SR1 - jar - - - 1.2 - 1.8.3_2 - - - - - - org.opendaylight.aaa - aaa-parent - ${project.version} - import - pom - - - - - - - com.google.code.findbugs - jsr305 - - - org.opendaylight.aaa - features-aaa - 0.3.1-Beryllium-SR1 - features - xml - - - org.opendaylight.aaa - aaa-shiro-act - 0.3.1-Beryllium-SR1 - - - org.opendaylight.aaa - aaa-shiro - 0.3.1-Beryllium-SR1 - cfg - configuration - - - org.opendaylight.aaa - aaa-shiro - - - org.opendaylight.aaa - aaa-authn-sts - 0.3.1-Beryllium-SR1 - - - org.opendaylight.aaa - aaa-authn-api - 0.3.1-Beryllium-SR1 - - - com.sun.jersey - jersey-servlet - - - com.sun.jersey - jersey-core - - - com.sun.jersey - jersey-server - provided - - - javax.servlet - javax.servlet-api - - - org.apache.felix - org.apache.felix.dependencymanager - - - org.apache.felix - org.apache.felix.metatype - - - com.google.guava - guava - - - org.opendaylight.aaa - aaa-shiro - - - org.opendaylight.aaa - aaa-authn - - - org.opendaylight.aaa - aaa-authn-api - - - org.opendaylight.aaa - aaa-authn-sts - - - javax.annotation - javax.annotation-api - ${javax.annotation.api.version} - - - org.apache.felix - org.apache.felix.dependencymanager - - - org.apache.felix - org.apache.felix.metatype - - - org.apache.shiro - shiro-web - - - org.apache.shiro - shiro-core - - - org.apache.servicemix.bundles - org.apache.servicemix.bundles.commons-beanutils - ${servicemix.version} - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.resourceserver - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.common - - - javax.ws.rs - javax.ws.rs-api - - - org.json - json - - - commons-codec - commons-codec - - - - - scm:git:ssh://git.opendaylight.org:29418/aaa.git - scm:git:ssh://git.opendaylight.org:29418/aaa.git - HEAD - https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary - - diff --git a/odl-aaa-moon/features/shiro/src/main/features/features.xml b/odl-aaa-moon/features/shiro/src/main/features/features.xml deleted file mode 100644 index c6073a2a..00000000 --- a/odl-aaa-moon/features/shiro/src/main/features/features.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - mvn:org.opendaylight.aaa/features-aaa/{{VERSION}}/xml/features - - - - - - mvn:org.apache.felix/org.apache.felix.dependencymanager/{{VERSION}} - mvn:org.apache.felix/org.apache.felix.metatype/{{VERSION}} - - - odl-aaa-authn - - mvn:org.apache.shiro/shiro-web/{{VERSION}} - mvn:org.apache.shiro/shiro-core/{{VERSION}} - - mvn:com.google.guava/guava/{{VERSION}} - wrap:mvn:javax.annotation/javax.annotation-api/{{VERSION}} - wrap:mvn:com.google.code.findbugs/jsr305/{{VERSION}} - wrap:mvn:commons-codec/commons-codec/{{VERSION}} - wrap:mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.resourceserver/{{VERSION}} - wrap:mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.authzserver/{{VERSION}} - wrap:mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/{{VERSION}} - wrap:mvn:org.json/json/{{VERSION}} - mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-beanutils/{{VERSION}} - mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}} - - - mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}}/cfg/configuration - - - diff --git a/odl-aaa-moon/parent/pom.xml b/odl-aaa-moon/parent/pom.xml deleted file mode 100644 index 37bf3ad2..00000000 --- a/odl-aaa-moon/parent/pom.xml +++ /dev/null @@ -1,278 +0,0 @@ - - - 4.0.0 - - org.opendaylight.odlparent - odlparent - 1.6.1-Beryllium-SR1 - - - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - pom - - 3.0.4 - - - - - 1.2.1-Beryllium-SR1 - 1.6.1-Beryllium-SR1 - - - 1.0.10 - - - ${project.version} - ${basedir} - - - 0.8.1-Beryllium-SR1 - src/main/yang-gen-config - src/main/yang-gen-sal - 2.0.1-Beryllium-SR1 - 0.8.1-Beryllium-SR1 - 1.3.1-Beryllium-SR1 - 1.3.1-Beryllium-SR1 - 0.4.1-Beryllium-SR1 - 08-authz-config.xml - 09-rest-connector.xml - etc/opendaylight/karaf - - - 1.0.4 - 2.8.3 - 1.1.1 - 1.0.0 - - 08-authn-config.xml - - - 1.4.185 - - - 4.4 - - - 1 - 7.0.0.M2 - 1.6.1-Beryllium-SR1 - - - - - - - org.opendaylight.aaa - aaa-artifacts - ${aaa.version} - pom - import - - - org.opendaylight.yangtools - yangtools-artifacts - ${yangtools.version} - pom - import - - - org.opendaylight.mdsal - mdsal-artifacts - ${mdsal.version} - import - pom - - - org.opendaylight.mdsal.model - mdsal-model-artifacts - ${mdsal.model.version} - import - pom - - - org.opendaylight.controller - mdsal-artifacts - ${controller.mdsal.version} - import - pom - - - org.opendaylight.controller - config-artifacts - ${config.version} - pom - import - - - - - org.glassfish - javax.json - ${glassfish.json.version} - - - org.apache.felix - org.apache.felix.metatype - ${osgi.metatype.version} - - - net.sf.ehcache - ehcache - ${ehcache.version} - - - org.apache.geronimo.specs - geronimo-jta_1.1_spec - ${jta.version} - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.common - ${oltu.version} - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - ${oltu.version} - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.resourceserver - ${oltu.version} - - - com.h2database - h2 - ${h2.version} - - - - - org.opendaylight.odlparent - features-test - ${features.test.version} - test - - - javax.inject - javax.inject - ${javax.inject.version} - test - - - org.eclipse.jetty - jetty-servlet-tester - ${servlet.tester.version} - test - - - - - - - - org.jacoco - jacoco-maven-plugin - - - org.opendaylight.aaa.* - - - - - pre-test - - prepare-agent - - - - post-test - - report - - test - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - false - true - checkstyle-logging.xml - true - true - ${project.basedir} - **\/*.java,**\/*.xml,**\/*.ini,**\/*.sh,**\/*.bat,**\/*.yang - **\/target\/,**\/bin\/,**\/target-ide\/,**\/src/main/yang-gen-config\/,**\/src/main/yang-gen-sal\/ - - - - - check - - process-sources - - - - - org.opendaylight.yangtools - checkstyle-logging - ${yangtools.version} - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - generate-sources - - add-source - - - - ${jmxGeneratorPath} - ${salGeneratorPath} - - - - - - - - - https://wiki.opendaylight.org/view/AAA:Main - - scm:git:ssh://git.opendaylight.org:29418/aaa.git - scm:git:ssh://git.opendaylight.org:29418/aaa.git - HEAD - - - - - - org.codehaus.mojo - findbugs-maven-plugin - ${findbugs.maven.plugin.version} - - Max - Low - site - - - - org.codehaus.mojo - jdepend-maven-plugin - ${jdepend.maven.plugin.version} - - - - diff --git a/odl-aaa-moon/pom.xml b/odl-aaa-moon/pom.xml deleted file mode 100644 index 3d6591e2..00000000 --- a/odl-aaa-moon/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - 4.0.0 - - org.opendaylight.aaa - aaa-parent - 0.3.1-Beryllium-SR1 - parent - - - org.opendaylight.aaa - aaa.project - 0.3.1-Beryllium-SR1 - pom - aaa - - 3.0 - - - - aaa-authn-api - aaa-authn - aaa-idp-mapping - aaa-authn-sts - aaa-authn-store - aaa-authn-federation - aaa-authn-sssd - aaa-authn-keystone - aaa-authn-basic - aaa-idmlight - aaa-authn-mdsal-store - aaa-authz - aaa-credential-store-api - artifacts - features - distribution-karaf - parent - aaa-shiro - aaa-shiro-act - aaa-h2-store - - - - scm:git:ssh://git.opendaylight.org:29418/aaa.git - scm:git:ssh://git.opendaylight.org:29418/aaa.git - HEAD - https://wiki.opendaylight.org/view/AAA:Main - - - -- 2.16.6