summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/moon_agent.py2
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/moon_mgrs/authz_mgr/authz_mgr.py14
-rw-r--r--odl-aaa-moon/README.md64
-rw-r--r--odl-aaa-moon/README.rst5
-rw-r--r--odl-aaa-moon/aaa-authn-api/pom.xml38
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile29
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.pngbin0 -> 30016 bytes
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.ucls127
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.pngbin0 -> 29197 bytes
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.wsd18
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.pngbin0 -> 40566 bytes
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.wsd24
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/mapping.rst1609
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.pngbin0 -> 38693 bytes
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.wsd25
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.diag6
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.svg32
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.diag18
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.svg79
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.diag31
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.svg143
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.diag25
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.svg100
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_05.svg613
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.pngbin0 -> 39322 bytes
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.wsd23
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_configuration.rst1687
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Authentication.java26
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationException.java31
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationService.java42
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Claim.java56
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClaimAuth.java37
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClientService.java20
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/CredentialAuth.java28
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Credentials.java15
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreException.java24
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreUtil.java40
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IIDMStore.java72
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IdMService.java39
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/PasswordCredentials.java20
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/SHA256Calculator.java83
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenAuth.java37
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenStore.java25
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Claim.java60
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domain.java86
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domains.java34
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grant.java86
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grants.java35
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/IDMError.java61
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Role.java86
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Roles.java34
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/User.java126
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/UserPwd.java40
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Users.java34
-rw-r--r--odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Version.java49
-rw-r--r--odl-aaa-moon/aaa-authn-basic/pom.xml76
-rw-r--r--odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java31
-rw-r--r--odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java129
-rw-r--r--odl-aaa-moon/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java102
-rw-r--r--odl-aaa-moon/aaa-authn-federation/pom.xml132
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/Activator.java51
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ClaimAuthFilter.java249
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationConfiguration.java95
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationEndpoint.java149
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ServiceLocator.java83
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/SssdFilter.java151
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.properties11
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.xml19
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/resources/WEB-INF/web.xml34
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/main/resources/federation.cfg3
-rw-r--r--odl-aaa-moon/aaa-authn-federation/src/test/java/org/opendaylight/aaa/federation/FederationEndpointTest.java121
-rw-r--r--odl-aaa-moon/aaa-authn-keystone/pom.xml106
-rw-r--r--odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/Activator.java34
-rw-r--r--odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/KeystoneTokenAuth.java39
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/pom.xml99
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/src/main/yang/aaa-authn-model.yang154
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/pom.xml40
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/src/main/resources/initial/08-authn-config.xml43
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/pom.xml169
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/AuthNStore.java263
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypter.java101
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMMDSALStore.java483
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMObject2MDSAL.java224
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMStore.java182
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtil.java140
-rw-r--r--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.java90
-rw-r--r--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.java46
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/yang/aaa-authn-mdsal-store-cfg.yang77
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataBrokerReadMocker.java112
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypterTest.java38
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTest.java175
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTestUtil.java181
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/MDSALConvertTest.java78
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/util/AuthNStoreUtilTest.java88
-rw-r--r--odl-aaa-moon/aaa-authn-mdsal-store/pom.xml22
-rw-r--r--odl-aaa-moon/aaa-authn-sssd/pom.xml88
-rw-r--r--odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/Activator.java28
-rw-r--r--odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/SssdClaimAuth.java220
-rw-r--r--odl-aaa-moon/aaa-authn-store/pom.xml100
-rw-r--r--odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/Activator.java45
-rw-r--r--odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/DefaultTokenStore.java154
-rw-r--r--odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.properties14
-rw-r--r--odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.xml22
-rw-r--r--odl-aaa-moon/aaa-authn-store/src/main/resources/tokens.cfg4
-rw-r--r--odl-aaa-moon/aaa-authn-store/src/test/java/org/opendaylight/aaa/store/DefaultTokenStoreTest.java66
-rw-r--r--odl-aaa-moon/aaa-authn-sts/pom.xml112
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/Activator.java207
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousPasswordValidator.java30
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousRefreshTokenValidator.java29
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/OAuthRequest.java42
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/ServiceLocator.java141
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenAuthFilter.java148
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenEndpoint.java242
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/main/resources/WEB-INF/web.xml23
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/RestFixture.java34
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenAuthTest.java94
-rw-r--r--odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenEndpointTest.java164
-rw-r--r--odl-aaa-moon/aaa-authn/pom.xml103
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java51
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java122
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java77
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java160
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java88
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java42
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java104
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java87
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java258
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties12
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml16
-rw-r--r--odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg2
-rw-r--r--odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java129
-rw-r--r--odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java133
-rw-r--r--odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java208
-rw-r--r--odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java70
-rw-r--r--odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java39
-rw-r--r--odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java191
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-config/pom.xml43
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-config/src/main/resources/initial/08-authz-config.xml60
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml95
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-model/src/main/yang/authorization-schema.yang190
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/pom.xml43
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/src/main/resources/initial/09-rest-connector.xml42
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml152
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzBrokerImpl.java150
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImpl.java46
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDataReadWriteTransaction.java129
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDomDataBroker.java100
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzProviderContextImpl.java47
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzReadOnlyTransaction.java69
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzServiceImpl.java121
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzWriteOnlyTransaction.java103
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModule.java76
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModuleFactory.java53
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/yang/aaa-authz-service-impl.yang115
-rw-r--r--odl-aaa-moon/aaa-authz/aaa-authz-service/src/test/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImplTest.java46
-rw-r--r--odl-aaa-moon/aaa-authz/pom.xml23
-rw-r--r--odl-aaa-moon/aaa-credential-store-api/pom.xml22
-rw-r--r--odl-aaa-moon/aaa-credential-store-api/src/main/yang/credential-model.yang47
-rw-r--r--odl-aaa-moon/aaa-h2-store/.gitignore2
-rw-r--r--odl-aaa-moon/aaa-h2-store/pom.xml160
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/config/IdmLightConfig.java133
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/AbstractStore.java187
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/DomainStore.java166
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/GrantStore.java158
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/H2Store.java316
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/RoleStore.java151
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/StoreException.java29
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/UserStore.java202
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModule.java49
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModuleFactory.java29
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/resources/initial/08-aaa-h2-store-config.xml26
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/main/yang/aaa-h2-store.yang28
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/DomainStoreTest.java76
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/GrantStoreTest.java76
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/H2StoreTest.java187
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/RoleStoreTest.java76
-rw-r--r--odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/UserStoreTest.java79
-rw-r--r--odl-aaa-moon/aaa-idmlight/pom.xml229
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java57
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java208
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java118
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java591
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java228
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java419
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java46
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java90
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java29
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml79
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py247
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml26
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang28
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java93
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/cleardb.sh5
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/domain.json5
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/domain2.json5
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/grant.json4
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/grant2.json4
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/result.json1
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/role-admin.json4
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/role-user.json4
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/test.sh308
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/user.json7
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/user2.json7
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/userpwd.json4
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/pom.xml84
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Activator.java25
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/IdpJson.java248
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidRuleException.java35
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidTypeException.java35
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidValueException.java35
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java1368
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/StatementErrorException.java35
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Token.java401
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/UndefinedValueException.java34
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/RuleProcessorTest.java130
-rw-r--r--odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/TokenTest.java66
-rw-r--r--odl-aaa-moon/aaa-shiro-act/pom.xml84
-rw-r--r--odl-aaa-moon/aaa-shiro-act/src/main/java/org/opendaylight/aaa/shiroact/Activator.java51
-rw-r--r--odl-aaa-moon/aaa-shiro-act/src/test/java/org/opendaylight/aaa/shiroact/ActivatorTest.java25
-rw-r--r--odl-aaa-moon/aaa-shiro/pom.xml156
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java45
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java94
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java38
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java78
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java170
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java72
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java187
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java78
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java155
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java30
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/resources/WEB-INF/web.xml48
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/resources/shiro.ini95
-rw-r--r--odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java45
-rw-r--r--odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java43
-rw-r--r--odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java106
-rw-r--r--odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java246
-rw-r--r--odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java139
-rw-r--r--odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java76
-rw-r--r--odl-aaa-moon/artifacts/pom.xml231
-rw-r--r--odl-aaa-moon/commons/docs/AuthNusecases.vsdbin0 -> 206336 bytes
-rw-r--r--odl-aaa-moon/commons/docs/direct_authn.pngbin0 -> 22058 bytes
-rw-r--r--odl-aaa-moon/commons/docs/federated_authn1.pngbin0 -> 36542 bytes
-rw-r--r--odl-aaa-moon/commons/docs/federated_authn2.pngbin0 -> 35203 bytes
-rw-r--r--odl-aaa-moon/commons/federation/README271
-rw-r--r--odl-aaa-moon/commons/federation/idp_mapping_rules.json.example30
-rw-r--r--odl-aaa-moon/commons/federation/jetty.xml.example85
-rw-r--r--odl-aaa-moon/commons/federation/my_app.conf.example31
-rw-r--r--odl-aaa-moon/commons/postman_examples/AAA_AuthZ_MDSAL.json.postman_collection77
-rw-r--r--odl-aaa-moon/distribution-karaf/pom.xml274
-rw-r--r--odl-aaa-moon/features/api/pom.xml91
-rw-r--r--odl-aaa-moon/features/api/src/main/features/features.xml21
-rw-r--r--odl-aaa-moon/features/authn/pom.xml304
-rw-r--r--odl-aaa-moon/features/authn/src/main/features/features.xml247
-rw-r--r--odl-aaa-moon/features/authz/pom.xml101
-rw-r--r--odl-aaa-moon/features/authz/src/main/features/features.xml31
-rw-r--r--odl-aaa-moon/features/pom.xml19
-rw-r--r--odl-aaa-moon/features/shiro/pom.xml179
-rw-r--r--odl-aaa-moon/features/shiro/src/main/features/features.xml41
-rw-r--r--odl-aaa-moon/parent/pom.xml278
-rw-r--r--odl-aaa-moon/pom.xml50
260 files changed, 28015 insertions, 8 deletions
diff --git a/keystonemiddleware-moon/keystonemiddleware/moon_agent.py b/keystonemiddleware-moon/keystonemiddleware/moon_agent.py
index 7e196ef6..fd878fea 100644
--- a/keystonemiddleware-moon/keystonemiddleware/moon_agent.py
+++ b/keystonemiddleware-moon/keystonemiddleware/moon_agent.py
@@ -82,7 +82,7 @@ class MoonAgentKeystoneMiddleware(object):
self.conf = conf
self._LOG = logging.getLogger(conf.get('log_name', __name__))
# FIXME: events are duplicated in log file
- moon_agent_fh = logging.FileHandler(CONF.moon_keystonemiddleware_agent["logfile"])
+ moon_agent_fh = logging.FileHandler(self.conf.get('logfile', "/tmp/keystonemiddleware.log"))
self._LOG.setLevel(logging.DEBUG)
self._LOG.addHandler(moon_agent_fh)
self._LOG.info(_LI('Starting Moon KeystoneMiddleware Agent'))
diff --git a/keystonemiddleware-moon/keystonemiddleware/moon_mgrs/authz_mgr/authz_mgr.py b/keystonemiddleware-moon/keystonemiddleware/moon_mgrs/authz_mgr/authz_mgr.py
index 0d81a790..9a0a4009 100644
--- a/keystonemiddleware-moon/keystonemiddleware/moon_mgrs/authz_mgr/authz_mgr.py
+++ b/keystonemiddleware-moon/keystonemiddleware/moon_mgrs/authz_mgr/authz_mgr.py
@@ -10,13 +10,13 @@ from oslo_config import cfg
_OPTS = [
- cfg.StrOpt('authz_mgr_login',
+ cfg.StrOpt('authz_login',
default="admin",
help='Name of the administrator who will connect to the Keystone Moon backends.'),
- cfg.StrOpt('authz_mgr_password',
+ cfg.StrOpt('authz_password',
default="nomoresecrete",
help='Password of the administrator who will connect to the Keystone Moon backends.'),
- cfg.StrOpt('authz_mgr_logfile',
+ cfg.StrOpt('logfile',
default="/tmp/moon_authz_mgr.log", # TODO: update in paste.init
help='File where logs goes.'),
]
@@ -34,11 +34,11 @@ class ServiceError(Exception):
class AuthzMgr(object):
def __init__(self, conf):
+ self.conf = conf
self._LOG = logging.getLogger(conf.get('log_name', __name__))
- authz_mgr_fh = logging.FileHandler(CONF.moon_authz_mgr["authz_mgr_logfile"])
+ authz_mgr_fh = logging.FileHandler(self.conf.get('logfile', "/tmp/keystonemiddleware.log"))
self._LOG.setLevel(logging.DEBUG)
self._LOG.addHandler(authz_mgr_fh)
- self._conf = conf
self.response_content = ""
def _deny_request(self, code):
@@ -67,7 +67,7 @@ class AuthzMgr(object):
self._LOG.debug('X-Auth-Token={}'.format(auth_token))
try:
_url = '{}/moon/authz/{}/{}/{}/{}'.format(
- self._conf["_request_uri"],
+ self.conf["_request_uri"],
agent_data['tenant_id'],
agent_data['user_id'],
agent_data['resource_id'],
@@ -75,7 +75,7 @@ class AuthzMgr(object):
self._LOG.info(_url)
response = requests.get(_url,
headers=headers,
- verify=self._conf["_verify"])
+ verify=self.conf["_verify"])
except requests.exceptions.RequestException as e:
self._LOG.error(_LI('HTTP connection exception: %s'), e)
resp = self._deny_request('InvalidURI')
diff --git a/odl-aaa-moon/README.md b/odl-aaa-moon/README.md
new file mode 100644
index 00000000..71f52a63
--- /dev/null
+++ b/odl-aaa-moon/README.md
@@ -0,0 +1,64 @@
+## 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://<controller>:<port>/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://<controller>:<port>/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
new file mode 100644
index 00000000..2ef4b3b7
--- /dev/null
+++ b/odl-aaa-moon/README.rst
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 00000000..31a17236
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-api</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile b/odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile
new file mode 100644
index 00000000..446795b4
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/Makefile
@@ -0,0 +1,29 @@
+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
new file mode 100644
index 00000000..999a41f9
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.png
Binary files differ
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
new file mode 100644
index 00000000..68345256
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/class_diagram.ucls
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<class-diagram version="1.1.6" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
+ realizations="true" associations="true" dependencies="true" nesting-relationships="true">
+ <interface id="1" language="java" name="org.opendaylight.aaa.api.TokenStore" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenStore.java" binary="false" corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="637" y="568"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <interface id="2" language="java" name="org.opendaylight.aaa.api.AuthenticationService" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationService.java" binary="false"
+ corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="385" y="727"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <interface id="3" language="java" name="org.opendaylight.aaa.api.CredentialAuth" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/CredentialAuth.java" binary="false"
+ corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="148" y="94"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <interface id="4" language="java" name="org.opendaylight.aaa.api.TokenAuth" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenAuth.java" binary="false" corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="139" y="568"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <interface id="5" language="java" name="org.opendaylight.aaa.api.PasswordCredentials" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/PasswordCredentials.java" binary="false"
+ corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="383" y="218"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <interface id="6" language="java" name="org.opendaylight.aaa.api.Credentials" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Credentials.java" binary="false" corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="385" y="93"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <interface id="7" language="java" name="org.opendaylight.aaa.api.Authentication" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Authentication.java" binary="false"
+ corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="386" y="567"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <interface id="8" language="java" name="org.opendaylight.aaa.api.ClaimAuth" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClaimAuth.java" binary="false" corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="138" y="386"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <interface id="9" language="java" name="org.opendaylight.aaa.api.Claim" project="aaa-authn-api"
+ file="/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Claim.java" binary="false" corner="BOTTOM_RIGHT">
+ <position height="-1" width="-1" x="386" y="386"/>
+ <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" accessors="true"
+ visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </display>
+ </interface>
+ <dependency id="10">
+ <end type="SOURCE" refId="3"/>
+ <end type="TARGET" refId="6"/>
+ </dependency>
+ <dependency id="11">
+ <end type="SOURCE" refId="2"/>
+ <end type="TARGET" refId="7"/>
+ </dependency>
+ <generalization id="12">
+ <end type="SOURCE" refId="5"/>
+ <end type="TARGET" refId="6"/>
+ </generalization>
+ <dependency id="13">
+ <end type="SOURCE" refId="3"/>
+ <end type="TARGET" refId="9"/>
+ </dependency>
+ <generalization id="14">
+ <end type="SOURCE" refId="7"/>
+ <end type="TARGET" refId="9"/>
+ </generalization>
+ <dependency id="15">
+ <end type="SOURCE" refId="1"/>
+ <end type="TARGET" refId="7"/>
+ </dependency>
+ <dependency id="16">
+ <end type="SOURCE" refId="8"/>
+ <end type="TARGET" refId="9"/>
+ </dependency>
+ <dependency id="17">
+ <end type="SOURCE" refId="4"/>
+ <end type="TARGET" refId="7"/>
+ </dependency>
+ <classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
+ accessors="true" visibility="true">
+ <attributes public="true" package="true" protected="true" private="false" static="true"/>
+ <operations public="true" package="true" protected="true" private="false" static="true"/>
+ </classifier-display>
+ <association-display labels="true" multiplicity="true"/>
+</class-diagram> \ 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
new file mode 100644
index 00000000..52d63650
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.png
Binary files differ
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
new file mode 100644
index 00000000..383d4031
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/credential_auth_sequence.wsd
@@ -0,0 +1,18 @@
+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
new file mode 100644
index 00000000..799cc909
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.png
Binary files differ
diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.wsd b/odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.wsd
new file mode 100644
index 00000000..22d1d916
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/federated_auth_sequence.wsd
@@ -0,0 +1,24 @@
+title Federated Authentication Sequence (w/ Claim Transformation)
+
+# This walks through the federated authentication sequence where a claim from a
+# third-party IdP system is posted to the ODL token endpoint in exchange for an
+# access token. The claim information is assumed to be in format specific to the
+# third-party IdP system and assumed to be captured via either Apache environment
+# variables (Servlet attributes) or HTTP headers.
+
+Client -> 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<String, Object> 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
new file mode 100644
index 00000000..33635502
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/mapping.rst
@@ -0,0 +1,1609 @@
+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<String, Object> |
++-----------+------------+--------------------+---------------------+
+| array | ARRAY | list | List<Object> |
++-----------+------------+--------------------+---------------------+
+| 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<username>\\w+)@(?P<domain>.+)"],
+ ["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<username>\\w+)@(?P<domain>.+)"],
+ ["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
new file mode 100644
index 00000000..728b86ce
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.png
Binary files differ
diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.wsd b/odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.wsd
new file mode 100644
index 00000000..3a1c1474
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/resource_access_sequence.wsd
@@ -0,0 +1,25 @@
+title Resource Access Sequence with Access Token
+
+ This walks through a listing request of a secured resource (MD-SAL topology)
+ from a client to the ODL controller using an access token (either one generated
+ by the ODL token endpoint, or a token from a third-party IdP) and shows how the
+ authentication context get set upon successful token validation. If token
+ validation fails, the TokenAuthFilter will return a 401, and the REST layer
+ will be oblivious to the failed request.
+
+Client -> 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
new file mode 100644
index 00000000..28317393
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.diag
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 00000000..4056b10a
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_01.svg
@@ -0,0 +1,32 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg viewBox="0 0 448 120" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs_block">
+ <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
+ <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
+ </filter>
+ </defs>
+ <title>blockdiag</title>
+ <desc>blockdiag {
+ User &lt;-&gt; AAA;
+ User [numbered = 1, shape = actor]
+ AAA [numbered = 2, label = "App Server\nAAA"]
+}
+
+</desc>
+ <polygon fill="rgb(0,0,0)" points="134,56 134,61 151,61 151,66 134,66 134,71 148,86 141,86 131,76 121,86 114,86 128,71 128,66 111,66 111,61 128,61 128,56" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
+ <ellipse cx="131" cy="51" fill="rgb(0,0,0)" rx="7" ry="7" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="46" />
+ <polygon fill="rgb(255,255,255)" points="131,50 131,55 148,55 148,60 131,60 131,65 145,80 138,80 128,70 118,80 111,80 125,65 125,60 108,60 108,55 125,55 125,50" stroke="rgb(0,0,0)" />
+ <ellipse cx="128" cy="45" fill="rgb(255,255,255)" rx="7" ry="7" stroke="rgb(0,0,0)" />
+ <ellipse cx="64" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="61" y="44">1</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="293" y="60">App Server</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="310" y="70">AAA</text>
+ <ellipse cx="256" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="253" y="44">2</text>
+ <path d="M 156 60 L 248 60" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="149,60 156,56 156,64 149,60" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="255,60 248,56 248,64 255,60" stroke="rgb(0,0,0)" />
+</svg>
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
new file mode 100644
index 00000000..2076dd16
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.diag
@@ -0,0 +1,18 @@
+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
new file mode 100644
index 00000000..42196b60
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_02.svg
@@ -0,0 +1,79 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg viewBox="0 0 594 200" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs_block">
+ <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
+ <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
+ </filter>
+ </defs>
+ <title>blockdiag</title>
+ <desc>blockdiag {
+ span_width = 30
+ User &lt;-&gt; Apache;
+ Proxy &lt;-&gt; AAA;
+ group {
+ Apache &lt;-&gt; Proxy;
+ group {
+ orientation = portrait
+ Apache &lt;-&gt; 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"]
+}
+
+</desc>
+ <rect fill="rgb(243,152,0)" height="140" style="filter:url(#filter_blur)" width="292" x="117" y="30" />
+ <rect fill="rgb(243,152,0)" height="140" style="filter:url(#filter_blur)" width="134" x="117" y="30" />
+ <polygon fill="rgb(0,0,0)" points="66,56 66,61 83,61 83,66 66,66 66,71 80,86 73,86 63,76 53,86 46,86 60,71 60,66 43,66 43,61 60,61 60,56" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
+ <ellipse cx="63" cy="51" fill="rgb(0,0,0)" rx="7" ry="7" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="123" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="123" y="126" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="281" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="439" y="46" />
+ <polygon fill="rgb(255,255,255)" points="63,50 63,55 80,55 80,60 63,60 63,65 77,80 70,80 60,70 50,80 43,80 57,65 57,60 40,60 40,55 57,55 57,50" stroke="rgb(0,0,0)" />
+ <ellipse cx="60" cy="45" fill="rgb(255,255,255)" rx="7" ry="7" stroke="rgb(0,0,0)" />
+ <ellipse cx="30" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="27" y="44">1</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="120" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="166" y="60">Apache</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="133" y="70">Authenticates user</text>
+ <ellipse cx="120" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="117" y="44">2</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="120" y="120" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="170" y="139">SSSD</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="138" y="149">Provides user info</text>
+ <ellipse cx="120" cy="120" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="117" y="124">3</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="278" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="300" y="59">Proxy Transport</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="289" y="71">Request + Metadata</text>
+ <ellipse cx="278" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="275" y="44">4</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="436" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="473" y="60">App Server</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="490" y="70">AAA</text>
+ <ellipse cx="436" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="433" y="44">5</text>
+ <path d="M 88 60 L 112 60" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="81,60 88,56 88,64 81,60" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="119,60 112,56 112,64 119,60" stroke="rgb(0,0,0)" />
+ <path d="M 414 60 L 428 60" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="407,60 414,56 414,64 407,60" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="435,60 428,56 428,64 435,60" stroke="rgb(0,0,0)" />
+ <path d="M 184 88 L 184 112" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="184,81 180,88 188,88 184,81" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="184,119 180,112 188,112 184,119" stroke="rgb(0,0,0)" />
+ <path d="M 256 60 L 270 60" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="249,60 256,56 256,64 249,60" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="277,60 270,56 270,64 277,60" stroke="rgb(0,0,0)" />
+ <path d="M 184 88 L 184 112" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="184,81 180,88 188,88 184,81" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="184,119 180,112 188,112 184,119" stroke="rgb(0,0,0)" />
+ <path d="M 256 60 L 270 60" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="249,60 256,56 256,64 249,60" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="277,60 270,56 270,64 277,60" stroke="rgb(0,0,0)" />
+</svg>
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
new file mode 100644
index 00000000..6ece3760
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.diag
@@ -0,0 +1,31 @@
+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
new file mode 100644
index 00000000..91e8b1be
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_03.svg
@@ -0,0 +1,143 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg viewBox="0 0 1024 1227" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs_block">
+ <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
+ <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
+ </filter>
+ </defs>
+ <title>blockdiag</title>
+ <desc>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 -&gt; Apache [label = "Request"];
+ === Apache mod_auth_kerb ===
+ Client &lt;- Apache [label = "401 Unauthorized"];
+ Client -&gt; Apache [label = "Authorization: Credentials"];
+ Apache -&gt; Apache [label = "Set\nUser Name\nAuth Type"];
+ === Apache mod_lookup_identity ===
+ Apache -&gt; SSSD [label = "Get User Info"];
+ SSSD --&gt; IdP [label = "Get User Info", leftnote = "Only if\nnot cached\nby SSSD"];
+ SSSD &lt;-- IdP [label = "Return User Info"];
+ Apache &lt;- SSSD [label = "Return User Info"];
+ Apache -&gt; Apache [label = "Set User specific\nenvironment\nvariables"];
+ === Apache mod_proxy ===
+ Apache -&gt; Container [label = "Proxy With User's Metadata"];
+ Apache &lt;- Container [label = "Response"];
+ Client &lt;- Apache [label = "Response"];
+
+}
+</desc>
+ <rect fill="rgb(0,0,0)" height="1065" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="127" y="140" />
+ <rect fill="rgb(0,0,0)" height="142" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="319" y="140" />
+ <rect fill="rgb(0,0,0)" height="815" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="319" y="344" />
+ <rect fill="rgb(0,0,0)" height="200" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="511" y="586" />
+ <rect fill="rgb(0,0,0)" height="70" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="703" y="654" />
+ <rect fill="rgb(0,0,0)" height="64" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="895" y="1031" />
+ <polygon fill="rgb(0,0,0)" points="420,636 491,636 499,644 499,672 420,672 420,636" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="643" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="835" y="46" />
+ <path d="M 128 80 L 128 1215" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
+ <rect fill="moccasin" height="1065" stroke="rgb(0,0,0)" width="8" x="124" y="134" />
+ <path d="M 320 80 L 320 1215" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
+ <rect fill="moccasin" height="142" stroke="rgb(0,0,0)" width="8" x="316" y="134" />
+ <rect fill="moccasin" height="815" stroke="rgb(0,0,0)" width="8" x="316" y="338" />
+ <path d="M 512 80 L 512 1215" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
+ <rect fill="moccasin" height="200" stroke="rgb(0,0,0)" width="8" x="508" y="580" />
+ <path d="M 704 80 L 704 1215" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
+ <rect fill="moccasin" height="70" stroke="rgb(0,0,0)" width="8" x="700" y="648" />
+ <path d="M 896 80 L 896 1215" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
+ <rect fill="moccasin" height="64" stroke="rgb(0,0,0)" width="8" x="892" y="1025" />
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="113" y="64">Client</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="302" y="65">Apache</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="498" y="64">SSSD</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="640" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="697" y="64">IdP</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="832" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="871" y="64">Container</text>
+ <path d="M 136 134 L 312 134" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="304,130 312,134 304,138" stroke="rgb(0,0,0)" />
+ <path d="M 136 276 L 312 276" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="144,272 136,276 144,280" stroke="rgb(0,0,0)" />
+ <path d="M 136 338 L 312 338" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="304,334 312,338 304,342" stroke="rgb(0,0,0)" />
+ <path d="M 328 422 L 416 422" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 416 422 L 416 438" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 416 438 L 328 438" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="336,434 328,438 336,442" stroke="rgb(0,0,0)" />
+ <path d="M 328 580 L 504 580" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="496,576 504,580 496,584" stroke="rgb(0,0,0)" />
+ <path d="M 520 648 L 696 648" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4" />
+ <polygon fill="rgb(0,0,0)" points="688,644 696,648 688,652" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(173,216,230)" points="417,630 488,630 496,638 496,666 417,666 417,630" stroke="rgb(0,0,0)" />
+ <path d="M 488 630 L 488 638" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 488 638 L 496 638" fill="none" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="425" y="642">Only if</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="425" y="652">not cached</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="425" y="664">by SSSD</text>
+ <path d="M 520 718 L 696 718" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4" />
+ <polygon fill="rgb(0,0,0)" points="528,714 520,718 528,722" stroke="rgb(0,0,0)" />
+ <path d="M 328 780 L 504 780" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="336,776 328,780 336,784" stroke="rgb(0,0,0)" />
+ <path d="M 328 864 L 416 864" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 416 864 L 416 880" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 416 880 L 328 880" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="336,876 328,880 336,884" stroke="rgb(0,0,0)" />
+ <path d="M 328 1025 L 888 1025" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="880,1021 888,1025 880,1029" stroke="rgb(0,0,0)" />
+ <path d="M 328 1089 L 888 1089" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="336,1085 328,1089 336,1093" stroke="rgb(0,0,0)" />
+ <path d="M 136 1153 L 312 1153" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="144,1149 136,1153 144,1157" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="140" y="132">Request</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="217" y="274">401 Unauthorized</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="140" y="336">Authorization: Credentials</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="328" y="398">Set</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="328" y="408">User Name</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="328" y="420">Auth Type</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="332" y="578">Get User Info</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="524" y="646">Get User Info</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="608" y="716">Return User Info</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="416" y="778">Return User Info</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="328" y="842">Set User specific</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="328" y="852">environment</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="328" y="862">variables</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="332" y="1023">Proxy With User's Metadata</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="841" y="1087">Response</text>
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="265" y="1151">Response</text>
+ <path d="M 40 202 L 442 202" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 40 206 L 442 206" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 581 202 L 984 202" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 581 206 L 984 206" fill="none" stroke="rgb(0,0,0)" />
+ <rect fill="rgb(208,208,208)" height="18" stroke="rgb(0,0,0)" width="139" x="442" y="195" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="452" y="209">Apache mod_auth_kerb</text>
+ <path d="M 40 506 L 429 506" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 40 510 L 429 510" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 594 506 L 984 506" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 594 510 L 984 510" fill="none" stroke="rgb(0,0,0)" />
+ <rect fill="rgb(208,208,208)" height="18" stroke="rgb(0,0,0)" width="165" x="429" y="499" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="439" y="513">Apache mod_lookup_identity</text>
+ <path d="M 40 948 L 455 948" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 40 952 L 455 952" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 568 948 L 984 948" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 568 952 L 984 952" fill="none" stroke="rgb(0,0,0)" />
+ <rect fill="rgb(208,208,208)" height="18" stroke="rgb(0,0,0)" width="113" x="455" y="941" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="465" y="955">Apache mod_proxy</text>
+</svg>
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
new file mode 100644
index 00000000..8f69a0b8
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.diag
@@ -0,0 +1,25 @@
+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
new file mode 100644
index 00000000..74850a85
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_04.svg
@@ -0,0 +1,100 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg viewBox="0 0 832 440" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs_block">
+ <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
+ <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
+ </filter>
+ </defs>
+ <title>blockdiag</title>
+ <desc>blockdiag {
+ Connector -&gt; SssdFilter;
+ SssdFilter -&gt; ClaimAuthFilter;
+ ClaimAuthFilter -&gt; SssdClaimAuth;
+ SssdClaimAuth -&gt; Assertion [folded];
+
+ group {
+ orientation = portrait
+ Assertion -&gt; JsonAssertion;
+ JsonAssertion -&gt; IdPMapper;
+ IdPMapper -&gt; JsonMapped;
+ }
+
+ JsonMapped -&gt; 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]
+}
+</desc>
+ <rect fill="rgb(243,152,0)" height="300" style="filter:url(#filter_blur)" width="144" x="56" y="110" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="643" y="46" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="126" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="206" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="286" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="366" />
+ <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="366" />
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="103" y="64">Connector</text>
+ <ellipse cx="64" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="61" y="44">1</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="294" y="64">SssdFilter</text>
+ <ellipse cx="256" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="253" y="44">2</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="471" y="64">ClaimAuthFilter</text>
+ <ellipse cx="448" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="445" y="44">3</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="640" y="40" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="665" y="64">SssdClaimAuth</text>
+ <ellipse cx="640" cy="40" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="637" y="44">4</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="120" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="103" y="144">Assertion</text>
+ <ellipse cx="64" cy="120" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="56" y="124">4.1</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="200" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="91" y="224">JsonAssertion</text>
+ <ellipse cx="64" cy="200" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="56" y="204">4.2</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="280" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="102" y="305">IdPMapper</text>
+ <ellipse cx="64" cy="280" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="56" y="284">4.3</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="360" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="97" y="385">JsonMapped</text>
+ <ellipse cx="64" cy="360" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="56" y="364">4.4</text>
+ <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="360" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="307" y="384">Claim</text>
+ <ellipse cx="256" cy="360" fill="pink" rx="12" ry="12" stroke="rgb(0,0,0)" />
+ <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="253" y="364">5</text>
+ <path d="M 192 60 L 248 60" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="255,60 248,56 248,64 255,60" stroke="rgb(0,0,0)" />
+ <path d="M 384 60 L 440 60" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="447,60 440,56 440,64 447,60" stroke="rgb(0,0,0)" />
+ <path d="M 576 60 L 632 60" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="639,60 632,56 632,64 639,60" stroke="rgb(0,0,0)" />
+ <path d="M 704 80 L 704 100" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 128 100 L 704 100" fill="none" stroke="rgb(0,0,0)" />
+ <path d="M 128 100 L 128 112" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="128,119 124,112 132,112 128,119" stroke="rgb(0,0,0)" />
+ <path d="M 128 160 L 128 192" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="128,199 124,192 132,192 128,199" stroke="rgb(0,0,0)" />
+ <path d="M 128 240 L 128 272" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="128,279 124,272 132,272 128,279" stroke="rgb(0,0,0)" />
+ <path d="M 128 320 L 128 352" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="128,359 124,352 132,352 128,359" stroke="rgb(0,0,0)" />
+ <path d="M 192 380 L 248 380" fill="none" stroke="rgb(0,0,0)" />
+ <polygon fill="rgb(0,0,0)" points="255,380 248,376 248,384 255,380" stroke="rgb(0,0,0)" />
+</svg>
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
new file mode 100644
index 00000000..f4657f06
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_05.svg
@@ -0,0 +1,613 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="689.19269"
+ height="212.05057"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.5 r10040"
+ sodipodi:docname="sssd_05.svg">
+ <defs
+ id="defs4">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient12785"
+ osb:paint="gradient">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop12787" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop12789" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient12777">
+ <stop
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ offset="0"
+ id="stop12779" />
+ <stop
+ style="stop-color:#ffcc00;stop-opacity:0;"
+ offset="1"
+ id="stop12781" />
+ </linearGradient>
+ <marker
+ inkscape:stockid="Scissors"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Scissors"
+ style="overflow:visible">
+ <path
+ id="schere"
+ d="M 9.0898857,-3.6061018 C 8.1198849,-4.7769976 6.3697607,-4.7358294 5.0623558,-4.2327734 l -8.2124046,3.0779029 c -2.3882933,-1.3067135 -4.7482873,-0.9325372 -4.7482873,-1.5687873 0,-0.4973164 0.4566662,-0.3883222 0.3883068,-1.6831941 -0.065635,-1.2432767 -1.3635771,-2.1630796 -2.5903987,-2.0816435 -1.227271,-0.00735 -2.499439,0.9331613 -2.510341,2.2300611 -0.09143,1.3063864 1.007209,2.5196896 2.306764,2.6052316 1.5223406,0.2266616 4.218258,-0.6955566 5.482945,1.57086006 -0.9422847,1.73825774 -2.6140244,1.74307674 -4.1255107,1.65607034 -1.2548743,-0.072235 -2.7620933,0.2873979 -3.3606483,1.5208605 -0.578367,1.1820862 -0.0112,2.8646022 1.316749,3.226412 1.3401912,0.4918277 3.1806689,-0.129711 3.4993722,-1.6707242 0.2456585,-1.187823 -0.5953659,-1.7459574 -0.2725074,-2.1771537 0.2436135,-0.32536 1.7907806,-0.1368452 4.5471053,-1.3748244 L 5.6763468,4.2330688 C 6.8000164,4.5467672 8.1730685,4.5362646 9.1684433,3.4313614 L -0.05164093,-0.05372222 9.0898857,-3.6061018 z m -18.3078016,-1.900504 c 1.294559,0.7227998 1.1888392,2.6835702 -0.1564272,3.0632889 -1.2165179,0.423661 -2.7710269,-0.7589694 -2.3831779,-2.0774648 0.227148,-1.0818519 1.653387,-1.480632 2.5396051,-0.9858241 z m 0.056264,8.0173649 c 1.3508301,0.4988648 1.1214429,2.7844356 -0.2522207,3.091609 -0.9110594,0.3163391 -2.2135494,-0.1387976 -2.3056964,-1.2121394 -0.177609,-1.305055 1.356085,-2.4841482 2.5579171,-1.8794696 z"
+ style="fill:#000000"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="DotL"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="DotL"
+ style="overflow:visible">
+ <path
+ id="path4170"
+ d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.8,0,0,0.8,5.92,0.8)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="StopL"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="StopL"
+ style="overflow:visible">
+ <path
+ id="path4278"
+ d="M 0,5.65 0,-5.65"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ transform="scale(0.8,0.8)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ id="path4133"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mend"
+ style="overflow:visible">
+ <path
+ id="path4136"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(-0.6,-0.6)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible">
+ <path
+ id="path4118"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible">
+ <path
+ id="path4130"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <filter
+ color-interpolation-filters="sRGB"
+ height="1.5039999"
+ id="filter_blur"
+ inkscape:collect="always"
+ width="1.1575"
+ x="-0.078749999"
+ y="-0.252">
+ <feGaussianBlur
+ id="feGaussianBlur3780"
+ inkscape:collect="always"
+ stdDeviation="4.2" />
+ </filter>
+ <marker
+ inkscape:stockid="Arrow2Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4133-8"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mend-1"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4136-9"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(-0.6,-0.6)" />
+ </marker>
+ <filter
+ color-interpolation-filters="sRGB"
+ height="1.5039999"
+ id="filter_blur-1"
+ inkscape:collect="always"
+ width="1.1575"
+ x="-0.078749999"
+ y="-0.252">
+ <feGaussianBlur
+ id="feGaussianBlur3780-1"
+ inkscape:collect="always"
+ stdDeviation="4.2" />
+ </filter>
+ <filter
+ inkscape:collect="always"
+ id="filter18355">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="6.2598764"
+ id="feGaussianBlur18357" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4"
+ inkscape:cx="405.52492"
+ inkscape:cy="110.18507"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:snap-grids="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="992"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-22.986913,-110.53072)">
+ <rect
+ y="136.89983"
+ x="254.85715"
+ height="185.19879"
+ width="456.83981"
+ id="rect12822"
+ style="fill:#f39800;fill-opacity:1;stroke:#000000;stroke-width:0.96499999999999997;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;filter:url(#filter18355)" />
+ <g
+ id="g18452">
+ <rect
+ y="244.58766"
+ x="105.58965"
+ height="41.710945"
+ width="129.83621"
+ id="rect2987"
+ style="fill:#ffffff;stroke:#000000;stroke-width:1.41119610999999989px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text2991"
+ y="261.25369"
+ x="112.20991"
+ style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ xml:space="preserve"><tspan
+ id="tspan2995"
+ y="261.25369"
+ x="112.20991"
+ sodipodi:role="line">Apache mod_proxy:</tspan><tspan
+ id="tspan2997"
+ y="276.25369"
+ x="112.20991"
+ sodipodi:role="line">forward port 8383</tspan></text>
+ </g>
+ <g
+ id="g18364">
+ <rect
+ y="167.43681"
+ x="304.33868"
+ height="50.483749"
+ width="98.582535"
+ id="rect2987-7"
+ style="fill:#ffffff;stroke:#000000;stroke-width:1.35282063000000008px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text2991-2"
+ y="181.34079"
+ x="353.99908"
+ style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ xml:space="preserve"><tspan
+ id="tspan2997-2"
+ y="181.34079"
+ x="353.99908"
+ sodipodi:role="line">Connector:</tspan><tspan
+ id="tspan3813"
+ y="196.34079"
+ x="353.99908"
+ sodipodi:role="line">port = 80</tspan><tspan
+ id="tspan3908"
+ y="211.34079"
+ x="353.99908"
+ sodipodi:role="line">(web)</tspan></text>
+ </g>
+ <flowRoot
+ xml:space="preserve"
+ id="flowRoot3815"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
+ id="flowRegion3817"><rect
+ id="rect3819"
+ width="201.02036"
+ height="90.913727"
+ x="174.25131"
+ y="117.466" /></flowRegion><flowPara
+ id="flowPara3821" /></flowRoot> <g
+ id="g18419">
+ <rect
+ y="240.20126"
+ x="304.33868"
+ height="50.483749"
+ width="98.582535"
+ id="rect2987-7-6"
+ style="fill:#ffffff;stroke:#000000;stroke-width:1.35282063000000008px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text2991-2-6"
+ y="253.64822"
+ x="353.63287"
+ style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ xml:space="preserve"><tspan
+ id="tspan2997-2-4"
+ y="253.64822"
+ x="353.63287"
+ sodipodi:role="line">Connector:</tspan><tspan
+ id="tspan3813-5"
+ y="268.64822"
+ x="353.63287"
+ sodipodi:role="line">port = 8383</tspan><tspan
+ id="tspan3908-2"
+ y="283.64822"
+ x="353.63287"
+ sodipodi:role="line">(auth proxy)</tspan></text>
+ </g>
+ <g
+ id="g7018"
+ transform="translate(-14,35.850205)">
+ <g
+ id="g7023"
+ transform="translate(218.19295,1.0101525)">
+ <g
+ id="g7028"
+ transform="translate(-97.984797,178.797)">
+ <polygon
+ id="polygon6858"
+ style="opacity:0.7;fill:#000000;fill-opacity:1;filter:url(#filter_blur)"
+ points="60,61 60,56 66,56 66,61 83,61 83,66 66,66 66,71 80,86 73,86 63,76 53,86 46,86 60,71 60,66 43,66 43,61 "
+ transform="translate(-115.02286,-17.004219)" />
+ <polygon
+ id="polygon6872"
+ points="57,55 57,50 63,50 63,55 80,55 80,60 63,60 63,65 77,80 70,80 60,70 50,80 43,80 57,65 57,60 40,60 40,55 "
+ style="fill:#ffffff;stroke:#000000"
+ transform="translate(-115.02286,-17.004219)" />
+ <ellipse
+ d="m 67,45 c 0,3.865993 -3.134007,7 -7,7 -3.865993,0 -7,-3.134007 -7,-7 0,-3.865993 3.134007,-7 7,-7 3.865993,0 7,3.134007 7,7 z"
+ id="ellipse6874"
+ ry="7"
+ rx="7"
+ cy="45"
+ cx="60"
+ sodipodi:cx="60"
+ sodipodi:cy="45"
+ sodipodi:rx="7"
+ sodipodi:ry="7"
+ style="fill:#ffffff;stroke:#000000"
+ transform="translate(-115.02286,-17.004219)" />
+ </g>
+ </g>
+ </g>
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:1.35282063000000008px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
+ id="rect2987-7-5"
+ width="98.582535"
+ height="50.483749"
+ x="589.35858"
+ y="239.54141" />
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ x="638.74945"
+ y="254.25693"
+ id="text2991-2-67"
+ sodipodi:linespacing="125%"><tspan
+ id="tspan10147"
+ sodipodi:role="line"
+ x="638.74945"
+ y="254.25693">AAA Servlet</tspan><tspan
+ id="tspan10204"
+ sodipodi:role="line"
+ x="638.74945"
+ y="269.25693">executes</tspan><tspan
+ id="tspan10206"
+ sodipodi:role="line"
+ x="638.74945"
+ y="284.25693">with roles</tspan></text>
+ <flowRoot
+ xml:space="preserve"
+ id="flowRoot10151"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
+ id="flowRegion10153"><rect
+ id="rect10155"
+ width="139.90613"
+ height="110.10663"
+ x="648.01288"
+ y="147.2655" /></flowRegion><flowPara
+ id="flowPara10157" /></flowRoot> <g
+ id="g18431">
+ <rect
+ y="169.04143"
+ x="589.86121"
+ height="50.483749"
+ width="98.582535"
+ id="rect2987-7-5-0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:1.35282063000000008px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text2991-2-67-9"
+ y="191.07236"
+ x="638.61047"
+ style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ xml:space="preserve"><tspan
+ y="191.07236"
+ x="638.61047"
+ sodipodi:role="line"
+ id="tspan10147-8">Non-AAA</tspan><tspan
+ y="206.07236"
+ x="638.61047"
+ sodipodi:role="line"
+ id="tspan10198">Servlet</tspan><tspan
+ y="221.07236"
+ x="638.61047"
+ sodipodi:role="line"
+ id="tspan10196" /></text>
+ </g>
+ <g
+ id="g18474">
+ <rect
+ y="168.30391"
+ x="437.00925"
+ height="122.27845"
+ width="121.29423"
+ id="rect2987-7-2-2"
+ style="fill:#ffffff;stroke:#000000;stroke-width:2.33539009000000020px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text2991-2-9-8"
+ y="181.0443"
+ x="497.75305"
+ style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ xml:space="preserve"><tspan
+ id="tspan3908-4-7"
+ y="181.0443"
+ x="497.75305"
+ sodipodi:role="line">ClaimAuthFilter:</tspan><tspan
+ id="tspan4038"
+ y="196.0443"
+ x="497.75305"
+ sodipodi:role="line">localPort in</tspan><tspan
+ id="tspan4040"
+ y="211.0443"
+ x="497.75305"
+ sodipodi:role="line">secureProxyPorts?</tspan><tspan
+ id="tspan4044"
+ y="226.0443"
+ x="497.75305"
+ sodipodi:role="line" /></text>
+ <g
+ id="g18469">
+ <rect
+ style="fill:#ff0000;stroke:#000000;stroke-width:0.81352955000000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;fill-opacity:1"
+ id="rect10241"
+ width="98.994949"
+ height="23.733509"
+ x="448.15887"
+ y="220.00537" />
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="488.27258"
+ y="236.16118"
+ id="text10243"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan10245"
+ x="488.27258"
+ y="236.16118">No</tspan></text>
+ </g>
+ <g
+ id="g18461">
+ <rect
+ style="fill:#00ff00;stroke:#000000;stroke-width:0.81352955000000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;fill-opacity:1"
+ id="rect10241-9"
+ width="98.994949"
+ height="23.733509"
+ x="448.15887"
+ y="253.81883" />
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="488.27258"
+ y="269.97464"
+ id="text10243-4"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan10245-6"
+ x="488.27258"
+ y="269.97464">Yes</tspan></text>
+ </g>
+ </g>
+ <g
+ id="g7018-9"
+ transform="translate(-15.11838,-36.914245)">
+ <g
+ id="g7023-0"
+ transform="translate(218.19295,1.0101525)">
+ <g
+ id="g7028-1"
+ transform="translate(-97.984797,178.797)">
+ <polygon
+ id="polygon6858-6"
+ style="opacity:0.7;fill:#000000;fill-opacity:1;filter:url(#filter_blur-1)"
+ points="60,71 60,66 43,66 43,61 60,61 60,56 66,56 66,61 83,61 83,66 66,66 66,71 80,86 73,86 63,76 53,86 46,86 "
+ transform="translate(-115.02286,-17.004219)" />
+ <polygon
+ id="polygon6872-6"
+ points="57,65 57,60 40,60 40,55 57,55 57,50 63,50 63,55 80,55 80,60 63,60 63,65 77,80 70,80 60,70 50,80 43,80 "
+ style="fill:#ffffff;stroke:#000000"
+ transform="translate(-115.02286,-17.004219)" />
+ <ellipse
+ d="m 67,45 c 0,3.865993 -3.134007,7 -7,7 -3.865993,0 -7,-3.134007 -7,-7 0,-3.865993 3.134007,-7 7,-7 3.865993,0 7,3.134007 7,7 z"
+ id="ellipse6874-1"
+ ry="7"
+ rx="7"
+ cy="45"
+ cx="60"
+ sodipodi:cx="60"
+ sodipodi:cy="45"
+ sodipodi:rx="7"
+ sodipodi:ry="7"
+ style="fill:#ffffff;stroke:#000000"
+ transform="translate(-115.02286,-17.004219)" />
+ </g>
+ </g>
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="430.15594"
+ y="119.6479"
+ id="text12879"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan12881"
+ x="430.15594"
+ y="119.6479">Java EE Container</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart-7);marker-end:url(#Arrow2Mend-1)"
+ d="m 57.185293,265.44314 48.404357,0"
+ id="path13365"
+ inkscape:connector-type="polyline"
+ inkscape:connector-curvature="0"
+ inkscape:connection-start="#g7018"
+ inkscape:connection-start-point="d4" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart-7);marker-end:url(#Arrow2Mend-1)"
+ d="m 235.42587,265.44314 68.91281,0"
+ id="path14574"
+ inkscape:connector-type="polyline"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart-7);marker-end:url(#Arrow2Mend-1)"
+ d="m 402.92122,265.52611 45.23767,0.0762"
+ id="path14999"
+ inkscape:connector-type="polyline"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart-7);marker-end:url(#Arrow1Mend)"
+ d="m 402.92122,206.09216 51.12769,13.91321"
+ id="path15397"
+ inkscape:connector-type="polyline"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart-7);marker-end:url(#Arrow2Mend-1)"
+ d="m 542.32654,220.00537 47.53467,-12.62771"
+ id="path15795"
+ inkscape:connector-type="polyline"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart-7);marker-end:url(#Arrow2Mend-1)"
+ d="m 547.15383,265.36883 42.20475,-0.2701"
+ id="path16193"
+ inkscape:connector-type="polyline"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart-7);marker-end:url(#Arrow2Mend-1)"
+ d="m 56.066913,192.67869 248.271767,0"
+ id="path17038"
+ inkscape:connector-type="polyline"
+ inkscape:connector-curvature="0"
+ inkscape:connection-start="#g7018-9"
+ inkscape:connection-start-point="d4" />
+ </g>
+</svg>
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
new file mode 100644
index 00000000..9f9a0b49
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.png
Binary files differ
diff --git a/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.wsd b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.wsd
new file mode 100644
index 00000000..f97ed1ee
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_auth_sequence.wsd
@@ -0,0 +1,23 @@
+title Federated Authentication with SSSD
+
+# This walks through the federated authentication sequence where a claim from a
+# third-party IdP system is posted to the ODL token endpoint in exchange for an
+# access token. The claim information is assumed to be in format specific to the
+# third-party IdP system and assumed to be captured via either Apache environment
+# variables (Servlet attributes) or HTTP headers.
+
+Client -> 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
new file mode 100644
index 00000000..7f912d94
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/docs/sssd_configuration.rst
@@ -0,0 +1,1687 @@
+################################################
+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:
+
+::
+
+ <Location my_resource>
+ AuthType Kerberos
+ AuthName "Kerberos Login"
+ KrbMethodNegotiate On
+ KrbMethodK5Passwd Off
+ KrbAuthRealms EXAMPLE.COM
+ Krb5KeyTab /etc/http.keytab
+ require valid-user
+ </Location>
+
+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.
+
+ ::
+
+ <LocationMatch "my_resource">
+ LookupUserAttr mail REMOTE_USER_EMAIL
+ </LocationMatch>
+
+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:
+
+ ::
+
+ <LocationMatch "my_resource">
+ LookupUserAttr mail AJP_REMOTE_USER_EMAIL
+ </LocationMatch>
+
+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.
+
+ ::
+
+ <LocationMatch "my_resource">
+ LookupUserAttr mail REMOTE_USER_EMAIL
+ RequestHeader set X-SSSD-REMOTE_USER_EMAIL %{REMOTE_USER_EMAIL}e
+ </LocationMatch>
+
+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:
+
+::
+
+ <LocationMatch "my_resource">
+
+ 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 ":"
+
+ </LocationMatch>
+
+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:
+
+::
+
+ <LocationMatch "my_resource">
+
+ 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
+
+ </LocationMatch>
+
+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.
+
+::
+
+ <Connector
+ address="127.0.0.1"
+ port="8383"
+ protocol="HTTP/1.1"
+ tomcatAuthentication="false"
+ connectionTimeout="20000"
+ redirectPort="8443"
+ />
+
+
+: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.
+
+::
+
+ <!-- Trusted Authentication Federation proxy connection -->
+ <Call name="addConnector">
+ <Arg>
+ <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+ <Set name="host">127.0.0.1</Set>
+ <Set name="port">8383</Set>
+ <Set name="maxIdleTime">300000</Set>
+ <Set name="Acceptors">2</Set>
+ <Set name="statsOn">false</Set>
+ <Set name="confidentialPort">8445</Set>
+ <Set name="name">federationConn</Set>
+ <Set name="lowResourcesConnections">20000</Set>
+ <Set name="lowResourcesMaxIdleTime">5000</Set>
+ </New>
+ </Arg>
+ </Call>
+
+: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:
+
+::
+
+ <Set name="host">
+ <Property name="jetty.host" default="127.0.0.1"/>
+ </Set>
+ <Set name="port">
+ <Property name="jetty.port" default="8383"/>
+ </Set>
+
+
+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]", "(?<username>\\w+)@(?<domain>.+)"],
+ 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:
+
+::
+
+ <!-- allow only LAN IPs to connect -->
+ <Valve className="org.apache.catalina.valves.RemoteAddrValve"
+ allow="192.168.1.*">
+ </Valve>
+
+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
new file mode 100644
index 00000000..25ba898b
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Authentication.java
@@ -0,0 +1,26 @@
+/*
+ * 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
new file mode 100644
index 00000000..d4621527
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationException.java
@@ -0,0 +1,31 @@
+/*
+ * 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
new file mode 100644
index 00000000..24ae9238
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/AuthenticationService.java
@@ -0,0 +1,42 @@
+/*
+ * 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
new file mode 100644
index 00000000..7d9a229a
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Claim.java
@@ -0,0 +1,56 @@
+/*
+ * 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<String> 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
new file mode 100644
index 00000000..447ffb35
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClaimAuth.java
@@ -0,0 +1,37 @@
+/*
+ * 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
+ * <code>"USER_NAME" -&gt; "joe".</code>
+ * <p>
+ * If there is no applicable claim information for the current
+ * implementation, this method should return a <code>null</code>.
+ * <p>
+ * 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<String, Object> 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
new file mode 100644
index 00000000..c11eec1c
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/ClientService.java
@@ -0,0 +1,20 @@
+/*
+ * 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
new file mode 100644
index 00000000..341e49ae
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/CredentialAuth.java
@@ -0,0 +1,28 @@
+/*
+ * 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<T extends Credentials> {
+
+ /**
+ * 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
new file mode 100644
index 00000000..7d2f19e5
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/Credentials.java
@@ -0,0 +1,15 @@
+/*
+ * 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
new file mode 100644
index 00000000..026c11ce
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreException.java
@@ -0,0 +1,24 @@
+/*
+ * 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
new file mode 100644
index 00000000..07dd522f
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IDMStoreUtil.java
@@ -0,0 +1,40 @@
+/*
+ * 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
new file mode 100644
index 00000000..7b031e05
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IIDMStore.java
@@ -0,0 +1,72 @@
+/*
+ * 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
new file mode 100644
index 00000000..1d698da5
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/IdMService.java
@@ -0,0 +1,39 @@
+/*
+ * 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<String> 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<String> 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
new file mode 100644
index 00000000..e5fa346d
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/PasswordCredentials.java
@@ -0,0 +1,20 @@
+/*
+ * 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
new file mode 100644
index 00000000..81f4b899
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/SHA256Calculator.java
@@ -0,0 +1,83 @@
+/*
+ * 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
new file mode 100644
index 00000000..bbf6fa2b
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenAuth.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ * <p>
+ * 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<String, List<String>> 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
new file mode 100644
index 00000000..4cd7aa78
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/TokenStore.java
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 00000000..180bddfb
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Claim.java
@@ -0,0 +1,60 @@
+/*
+ * 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<Role> 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<Role> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List<Role> 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
new file mode 100644
index 00000000..a42e0b6d
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domain.java
@@ -0,0 +1,86 @@
+/*
+ * 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
new file mode 100644
index 00000000..a8f2064b
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Domains.java
@@ -0,0 +1,34 @@
+/*
+ * 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<Domain> domains = new ArrayList<Domain>();
+
+ public void setDomains(List<Domain> domains) {
+ this.domains = domains;
+ }
+
+ public List<Domain> 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
new file mode 100644
index 00000000..20c2d128
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grant.java
@@ -0,0 +1,86 @@
+/*
+ * 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
new file mode 100644
index 00000000..ce0d9b85
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Grants.java
@@ -0,0 +1,35 @@
+/*
+ * 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<Grant> grants = new ArrayList<Grant>();
+
+ public void setGrants(List<Grant> grants) {
+ this.grants = grants;
+ }
+
+ public List<Grant> 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
new file mode 100644
index 00000000..f44c43d9
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/IDMError.java
@@ -0,0 +1,61 @@
+/*
+ * 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
new file mode 100644
index 00000000..de707496
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Role.java
@@ -0,0 +1,86 @@
+/*
+ * 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
new file mode 100644
index 00000000..33521028
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Roles.java
@@ -0,0 +1,34 @@
+/*
+ * 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<Role> roles = new ArrayList<Role>();
+
+ public void setRoles(List<Role> roles) {
+ this.roles = roles;
+ }
+
+ public List<Role> 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
new file mode 100644
index 00000000..c6c1f9a6
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/User.java
@@ -0,0 +1,126 @@
+/*
+ * 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
new file mode 100644
index 00000000..4750616d
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/UserPwd.java
@@ -0,0 +1,40 @@
+/*
+ * 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
new file mode 100644
index 00000000..a0a001bd
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Users.java
@@ -0,0 +1,34 @@
+/*
+ * 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<User> users = new ArrayList<User>();
+
+ public void setUsers(List<User> users) {
+ this.users = users;
+ }
+
+ public List<User> 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
new file mode 100644
index 00000000..a88c1f80
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-api/src/main/java/org/opendaylight/aaa/api/model/Version.java
@@ -0,0 +1,49 @@
+/*
+ * 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
new file mode 100644
index 00000000..f98e6294
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-basic/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-basic</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.aaa.basic.Activator</Bundle-Activator>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..bd57c9d3
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java
@@ -0,0 +1,31 @@
+/*
+ * 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
new file mode 100644
index 00000000..eff47e63
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java
@@ -0,0 +1,129 @@
+/*
+ * 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:
+ * <code>Authorization: Basic BASE_64_ENCODED_CREDENTIALS</code>
+ *
+ * Where <code>BASE_64_ENCODED_CREDENTIALS</code> is the base 64 encoded value
+ * of the user's credentials in the following form: <code>user:password</code>
+ *
+ * For example, assuming the user is "admin" and the password is "admin":
+ * <code>Authorization: Basic YWRtaW46YWRtaW4=</code>
+ *
+ * @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<PasswordCredentials> credentialAuth;
+
+ private static boolean checkAuthHeaderFormat(final String authHeader) {
+ return (authHeader != null && authHeader.startsWith(BASIC_PREFIX));
+ }
+
+ private static String extractAuthHeader(final Map<String, List<String>> 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<PasswordCredentials> 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<String, List<String>> 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
new file mode 100644
index 00000000..4ee439df
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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<String, List<String>> 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<String, List<String>> 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<String, List<String>> 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<String, List<String>> 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<String, List<String>> 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
new file mode 100644
index 00000000..0e84e185
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/pom.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-federation</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.common</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>com.sun.jersey.jersey-test-framework</groupId>
+ <artifactId>jersey-test-framework-grizzly2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet-tester</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>*,com.sun.jersey.spi.container.servlet</Import-Package>
+ <Web-ContextPath>/oauth2/federation</Web-ContextPath>
+ <Web-Connectors>federationConn</Web-Connectors>
+ <Bundle-Activator>org.opendaylight.aaa.federation.Activator</Bundle-Activator>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/federation.cfg</file>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
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
new file mode 100644
index 00000000..4ae027c8
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/Activator.java
@@ -0,0 +1,51 @@
+/*
+ * 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
+ * <code>CredentialAuth</code> 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<String, ?> addPid(Dictionary<String, String> 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
new file mode 100644
index 00000000..10a1277d
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ClaimAuthFilter.java
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ * <p>
+ * 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<Integer> 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<ClaimAuth> 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<String, Object> claims(HttpServletRequest req) {
+ String name;
+ Object objectValue;
+ String stringValue;
+ Map<String, Object> 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<String> 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
new file mode 100644
index 00000000..a68dc15c
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationConfiguration.java
@@ -0,0 +1,95 @@
+/*
+ * 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<String, String> defaults = new Hashtable<>();
+ static {
+ defaults.put(HTTP_HEADERS, "");
+ defaults.put(HTTP_ATTRIBUTES, "");
+ }
+ private static Map<String, String> configs = new ConcurrentHashMap<>();
+
+ // singleton
+ private FederationConfiguration() {
+ }
+
+ public static FederationConfiguration instance() {
+ return instance;
+ }
+
+ @Override
+ public void updated(Dictionary<String, ?> props) throws ConfigurationException {
+ if (props == null) {
+ configs.clear();
+ configs.putAll(defaults);
+ } else {
+ try {
+ Enumeration<String> 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<String> httpHeaders() {
+ String headers = configs.get(HTTP_HEADERS);
+ return (headers == null) ? new ArrayList<String>() : Arrays.asList(headers.split(" "));
+ }
+
+ public List<String> httpAttributes() {
+ String attributes = configs.get(HTTP_ATTRIBUTES);
+ return (attributes == null) ? new ArrayList<String>() : Arrays
+ .asList(attributes.split(" "));
+ }
+
+ public Set<Integer> secureProxyPorts() {
+ String ports = configs.get(SECURE_PROXY_PORTS);
+ Set<Integer> secureProxyPorts = new TreeSet<Integer>();
+
+ 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
new file mode 100644
index 00000000..6ac76c0a
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/FederationEndpoint.java
@@ -0,0 +1,149 @@
+/*
+ * 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<String> 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
new file mode 100644
index 00000000..dd861514
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/ServiceLocator.java
@@ -0,0 +1,83 @@
+/*
+ * 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<ClaimAuth> 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<ClaimAuth> getClaimAuthCollection() {
+ return claimAuthCollection;
+ }
+
+ public void setClaimAuthCollection(List<ClaimAuth> 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
new file mode 100644
index 00000000..9223c6dd
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/java/org/opendaylight/aaa/federation/SssdFilter.java
@@ -0,0 +1,151 @@
+/*
+ * 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
new file mode 100644
index 00000000..4323c04d
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 00000000..e2efd3d4
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/resources/OSGI-INF/metatype/metatype.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metatype:MetaData xmlns:metatype="http://www.osgi.org/xmlns/metatype/v1.0.0"
+ localization="OSGI-INF/metatype/metatype">
+ <OCD id="org.opendaylight.aaa.federation" name="%org.opendaylight.aaa.federation.name"
+ description="%org.opendaylight.aaa.federation.description">
+ <AD id="httpHeaders" type="String" default=""
+ name="%org.opendaylight.aaa.federation.httpHeaders.name"
+ description="%org.opendaylight.aaa.federation.httpHeaders.description" />
+ <AD id="httpAttributes" type="String" default=""
+ name="%org.opendaylight.aaa.federation.httpAttributes.name"
+ description="%org.opendaylight.aaa.federation.httpAttributes.description" />
+ <AD id="secureProxyPorts" type="String" default=""
+ name="%org.opendaylight.aaa.federation.secureProxyPorts.name"
+ description="%org.opendaylight.aaa.federation.secureProxyPorts.description" />
+ </OCD>
+ <Designate pid="org.opendaylight.aaa.federation">
+ <Object ocdref="org.opendaylight.aaa.federation" />
+ </Designate>
+</metatype:MetaData>
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
new file mode 100644
index 00000000..9fd9751f
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <servlet>
+ <servlet-name>federation</servlet-name>
+ <servlet-class>org.opendaylight.aaa.federation.FederationEndpoint</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>federation</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <!-- Federation Auth filter -->
+ <filter>
+ <filter-name>SssdFilter</filter-name>
+ <filter-class>org.opendaylight.aaa.federation.SssdFilter</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>SssdFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+ <filter>
+ <filter-name>ClaimAuthFilter</filter-name>
+ <filter-class>org.opendaylight.aaa.federation.ClaimAuthFilter</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>ClaimAuthFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+</web-app>
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
new file mode 100644
index 00000000..60ef1c46
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/main/resources/federation.cfg
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 00000000..ae098652
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-federation/src/test/java/org/opendaylight/aaa/federation/FederationEndpointTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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<Integer>(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
new file mode 100644
index 00000000..ee1d3278
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-keystone/pom.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-keystone</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore-osgi</artifactId>
+ <version>${httpclient.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient-osgi</artifactId>
+ <version>${httpclient.version}</version>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>com.sun.jersey.jersey-test-framework</groupId>
+ <artifactId>jersey-test-framework-grizzly2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.aaa.keystone.Activator</Bundle-Activator>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..c3c3bfb1
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/Activator.java
@@ -0,0 +1,34 @@
+/*
+ * 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
new file mode 100644
index 00000000..6f4b4bb1
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-keystone/src/main/java/org/opendaylight/aaa/keystone/KeystoneTokenAuth.java
@@ -0,0 +1,39 @@
+/*
+ * 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<String, List<String>> 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
new file mode 100644
index 00000000..fede7e5e
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/pom.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-mdsal-api</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>yang-binding</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>ietf-inet-types</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>ietf-yang-types</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>yang-ext</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${bundle.plugin.version}</version>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <stylesheet>maven</stylesheet>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>aggregate</goal>
+ </goals>
+ <phase>site</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>generate-sources</goal>
+ </goals>
+ <configuration>
+ <yangFilesRootDir>src/main/yang</yangFilesRootDir>
+ <codeGenerators>
+ <generator>
+ <codeGeneratorClass>
+ org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl
+ </codeGeneratorClass>
+ <outputBaseDir>${salGeneratorPath}</outputBaseDir>
+ </generator>
+ </codeGenerators>
+ <inspectDependencies>true</inspectDependencies>
+ </configuration>
+ </execution>
+ </executions>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>maven-sal-api-gen-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <type>jar</type>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+ <packaging>bundle</packaging>
+
+</project>
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
new file mode 100644
index 00000000..227cb313
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-api/src/main/yang/aaa-authn-model.yang
@@ -0,0 +1,154 @@
+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
new file mode 100644
index 00000000..f01969a4
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/pom.xml
@@ -0,0 +1,40 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-mdsal-config</artifactId>
+ <description>AuthN Token Store Service Configuration file </description>
+ <packaging>jar</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/initial/${config.authn.store.configfile}</file>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..e4a78f4d
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-config/src/main/resources/initial/08-authn-config.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ 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
+-->
+<snapshot>
+ <configuration>
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+
+ <!-- defines an implementation module -->
+ <module>
+ <type xmlns:authn="config:aaa:authn:mdsal:store">authn:aaa-authn-mdsal-store</type>
+ <name>aaa-authn-mdsal-store</name>
+ <dom-broker>
+ <type xmlns:dom="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">
+ dom:dom-broker-osgi-registry
+ </type>
+ <name>dom-broker</name>
+ </dom-broker>
+ <data-broker>
+ <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">
+ binding:binding-async-data-broker
+ </type>
+ <name>binding-data-broker</name>
+ </data-broker>
+ <timeToLive>3600000</timeToLive>
+ <timeToWait>15</timeToWait>
+ <password>CHANGE_ME</password>
+ </module>
+ </modules>
+ </data>
+
+ </configuration>
+ <required-capabilities>
+ <capability>config:aaa:authn:mdsal:store?module=aaa-authn-mdsal-store-cfg&amp;revision=2014-10-31</capability>
+ </required-capabilities>
+
+</snapshot>
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
new file mode 100644
index 00000000..c36febee
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/pom.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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
+ ~
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-mdsal-store-impl</artifactId>
+ <packaging>bundle</packaging>
+
+ <properties>
+ <powermock.version>1.5.2</powermock.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-data-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-core-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-mdsal-api</artifactId>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <!-- <Bundle-Activator>/Bundle-Activator> -->
+ <Export-Package>org.opendaylight.yang.gen.v1.config.aaa.authn.mdsal.store.*
+ </Export-Package>
+ </instructions>
+ </configuration>
+ <!-- <configuration> <Export-Package> </Export-Package> </configuration> -->
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <executions>
+ <execution>
+ <id>config</id>
+ <goals>
+ <goal>generate-sources</goal>
+ </goals>
+ <configuration>
+ <codeGenerators>
+ <generator>
+ <codeGeneratorClass>
+ org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ </codeGeneratorClass>
+ <outputBaseDir>${jmxGeneratorPath}</outputBaseDir>
+ <additionalConfiguration>
+ <namespaceToPackage1>
+ urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang
+ </namespaceToPackage1>
+ </additionalConfiguration>
+ </generator>
+ <generator>
+ <codeGeneratorClass>org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl</codeGeneratorClass>
+ <outputBaseDir>${salGeneratorPath}</outputBaseDir>
+ </generator>
+ </codeGenerators>
+ <inspectDependencies>true</inspectDependencies>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>yang-jmx-generator-plugin</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>maven-sal-api-gen-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+
+
+</project>
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
new file mode 100644
index 00000000..09170182
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/AuthNStore.java
@@ -0,0 +1,263 @@
+/*
+ * 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> claims_iid = AuthNStoreUtil.createInstIdentifierForTokencache(claims.getToken());
+ WriteTransaction tx = broker.newWriteOnlyTransaction();
+ tx.put(LogicalDatastoreType.OPERATIONAL, claims_iid, claims, true);
+
+ final InstanceIdentifier<UserTokens> userTokens_iid = AuthNStoreUtil.createInstIdentifierUserTokens(
+ tokenlist.getUserId(), usertokens.getTokenid());
+ tx.put(LogicalDatastoreType.OPERATIONAL, userTokens_iid, usertokens, true);
+
+ CheckedFuture<Void, TransactionCommitFailedException> commitFuture = tx.submit();
+ Futures.addCallback(commitFuture, new FutureCallback<Void>() {
+
+ @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> claims_iid = AuthNStoreUtil.createInstIdentifierForTokencache(token);
+ Claims claims = null;
+ ReadTransaction rt = broker.newReadOnlyTransaction();
+ CheckedFuture<Optional<Claims>, ReadFailedException> claimsFuture = rt.read(
+ LogicalDatastoreType.OPERATIONAL, claims_iid);
+ try {
+ Optional<Claims> 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> tokenList_iid = InstanceIdentifier.builder(
+ TokenCacheTimes.class).child(TokenList.class, new TokenListKey(userId)).build();
+ TokenList tokenList = null;
+ ReadTransaction rt = broker.newReadOnlyTransaction();
+ CheckedFuture<Optional<TokenList>, ReadFailedException> userTokenListFuture = rt.read(
+ LogicalDatastoreType.OPERATIONAL, tokenList_iid);
+ try {
+ Optional<TokenList> 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> userTokens_iid = AuthNStoreUtil.createInstIdentifierUserTokens(
+ userId, token);
+ UserTokens userTokens = null;
+
+ ReadTransaction rt = broker.newReadOnlyTransaction();
+ CheckedFuture<Optional<UserTokens>, ReadFailedException> userTokensFuture = rt.read(
+ LogicalDatastoreType.OPERATIONAL, userTokens_iid);
+
+ try {
+ Optional<UserTokens> 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> claims_iid = AuthNStoreUtil.createInstIdentifierForTokencache(token);
+ boolean result = false;
+ WriteTransaction tx = broker.newWriteOnlyTransaction();
+ tx.delete(LogicalDatastoreType.OPERATIONAL, claims_iid);
+ CheckedFuture<Void, TransactionCommitFailedException> 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> userTokens_iid = AuthNStoreUtil.createInstIdentifierUserTokens(
+ userId, token);
+
+ WriteTransaction tx = broker.newWriteOnlyTransaction();
+ tx.delete(LogicalDatastoreType.OPERATIONAL, userTokens_iid);
+ CheckedFuture<Void, TransactionCommitFailedException> 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
new file mode 100644
index 00000000..ca0a74be
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypter.java
@@ -0,0 +1,101 @@
+/*
+ * 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
new file mode 100644
index 00000000..88fba0ba
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMMDSALStore.java
@@ -0,0 +1,483 @@
+/*
+ * 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<Void, TransactionCommitFailedException> 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<Domain> ID = InstanceIdentifier.create(Authentication.class).child(
+ Domain.class, new DomainKey(domain.getDomainid()));
+ WriteTransaction wrt = dataBroker.newWriteOnlyTransaction();
+ wrt.put(LogicalDatastoreType.CONFIGURATION, ID, domain, true);
+ CheckedFuture<Void, TransactionCommitFailedException> submit = wrt.submit();
+ if (!waitForSubmit(submit)) {
+ return domain;
+ } else {
+ return null;
+ }
+ }
+
+ public Domain readDomain(String domainid) {
+ Preconditions.checkNotNull(domainid);
+ InstanceIdentifier<Domain> ID = InstanceIdentifier.create(Authentication.class).child(
+ Domain.class, new DomainKey(domainid));
+ ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
+ CheckedFuture<Optional<Domain>, ReadFailedException> read = rot.read(
+ LogicalDatastoreType.CONFIGURATION, ID);
+ if (read == null) {
+ LOG.error("Failed to read domain from data store");
+ return null;
+ }
+ Optional<Domain> 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<Domain> 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<Domain> getAllDomains() {
+ InstanceIdentifier<Authentication> id = InstanceIdentifier.create(Authentication.class);
+ ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
+ CheckedFuture<Optional<Authentication>, 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<Role> getAllRoles() {
+ InstanceIdentifier<Authentication> id = InstanceIdentifier.create(Authentication.class);
+ ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
+ CheckedFuture<Optional<Authentication>, 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<User> getAllUsers() {
+ InstanceIdentifier<Authentication> id = InstanceIdentifier.create(Authentication.class);
+ ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
+ CheckedFuture<Optional<Authentication>, 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<Grant> getAllGrants() {
+ InstanceIdentifier<Authentication> id = InstanceIdentifier.create(Authentication.class);
+ ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
+ CheckedFuture<Optional<Authentication>, 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<Role> ID = InstanceIdentifier.create(Authentication.class).child(
+ Role.class, new RoleKey(role.getRoleid()));
+ WriteTransaction wrt = dataBroker.newWriteOnlyTransaction();
+ wrt.put(LogicalDatastoreType.CONFIGURATION, ID, role, true);
+ CheckedFuture<Void, TransactionCommitFailedException> submit = wrt.submit();
+ if (!waitForSubmit(submit)) {
+ return role;
+ } else {
+ return null;
+ }
+ }
+
+ public Role readRole(String roleid) {
+ Preconditions.checkNotNull(roleid);
+ InstanceIdentifier<Role> ID = InstanceIdentifier.create(Authentication.class).child(
+ Role.class, new RoleKey(roleid));
+ ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
+ CheckedFuture<Optional<Role>, ReadFailedException> read = rot.read(
+ LogicalDatastoreType.CONFIGURATION, ID);
+ if (read == null) {
+ LOG.error("Failed to read role from data store");
+ return null;
+ }
+ Optional<Role> 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<Role> 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<User> ID = InstanceIdentifier.create(Authentication.class).child(
+ User.class, new UserKey(user.getUserid()));
+ WriteTransaction wrt = dataBroker.newWriteOnlyTransaction();
+ wrt.put(LogicalDatastoreType.CONFIGURATION, ID, user, true);
+ CheckedFuture<Void, TransactionCommitFailedException> submit = wrt.submit();
+ if (!waitForSubmit(submit)) {
+ return user;
+ } else {
+ return null;
+ }
+ }
+
+ public User readUser(String userid) {
+ Preconditions.checkNotNull(userid);
+ InstanceIdentifier<User> ID = InstanceIdentifier.create(Authentication.class).child(
+ User.class, new UserKey(userid));
+ ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
+ CheckedFuture<Optional<User>, ReadFailedException> read = rot.read(
+ LogicalDatastoreType.CONFIGURATION, ID);
+ if (read == null) {
+ LOG.error("Failed to read user from data store");
+ return null;
+ }
+ Optional<User> 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<User> 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<Grant> ID = InstanceIdentifier.create(Authentication.class).child(
+ Grant.class, new GrantKey(grant.getGrantid()));
+ WriteTransaction wrt = dataBroker.newWriteOnlyTransaction();
+ wrt.put(LogicalDatastoreType.CONFIGURATION, ID, grant, true);
+ CheckedFuture<Void, TransactionCommitFailedException> submit = wrt.submit();
+ if (!waitForSubmit(submit)) {
+ return grant;
+ } else {
+ return null;
+ }
+ }
+
+ public Grant readGrant(String grantid) {
+ Preconditions.checkNotNull(grantid);
+ InstanceIdentifier<Grant> ID = InstanceIdentifier.create(Authentication.class).child(
+ Grant.class, new GrantKey(grantid));
+ ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction();
+ CheckedFuture<Optional<Grant>, ReadFailedException> read = rot.read(
+ LogicalDatastoreType.CONFIGURATION, ID);
+ if (read == null) {
+ LOG.error("Failed to read grant from data store");
+ return null;
+ }
+ Optional<Grant> 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<Grant> 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
new file mode 100644
index 00000000..0b58ced7
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMObject2MDSAL.java
@@ -0,0 +1,224 @@
+/*
+ * 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<Class<?>, ConvertionMethods> typesMethods = new HashMap<Class<?>, 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<Method> setMethods = new ArrayList<Method>();
+ private Map<String, Method> getMethods = new HashMap<String, Method>();
+ 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
new file mode 100644
index 00000000..69bc1d52
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/java/org/opendaylight/aaa/authn/mdsal/store/IDMStore.java
@@ -0,0 +1,182 @@
+/*
+ * 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<org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Domain> 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<org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Role> 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<org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User> 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<org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.User> 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<org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant> 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<org.opendaylight.yang.gen.v1.urn.aaa.yang.authn.claims.rev141029.authentication.Grant> 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
new file mode 100644
index 00000000..6ef58109
--- /dev/null
+++ 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
@@ -0,0 +1,140 @@
+/*
+ * 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<Claims> createInstIdentifierForTokencache(String token) {
+ if (token == null || token.length() == 0)
+ return null;
+
+ InstanceIdentifier<Claims> claims_iid = InstanceIdentifier.builder(Tokencache.class)
+ .child(Claims.class,
+ new ClaimsKey(token))
+ .build();
+ return claims_iid;
+ }
+
+ public static InstanceIdentifier<UserTokens> createInstIdentifierUserTokens(String userId,
+ String token) {
+ if (userId == null || userId.length() == 0 || token == null || token.length() == 0)
+ return null;
+
+ InstanceIdentifier<UserTokens> 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<String> roles = new ArrayList<String>();
+ 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> userTokens = new ArrayList<UserTokens>();
+ 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<String> 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
new file mode 100644
index 00000000..0631170e
--- /dev/null
+++ 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
@@ -0,0 +1,90 @@
+/*
+ * 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
new file mode 100644
index 00000000..b1e278fa
--- /dev/null
+++ 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
@@ -0,0 +1,46 @@
+/*
+ * 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
new file mode 100644
index 00000000..eac344b8
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/main/yang/aaa-authn-mdsal-store-cfg.yang
@@ -0,0 +1,77 @@
+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
new file mode 100644
index 00000000..f821cf16
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataBrokerReadMocker.java
@@ -0,0 +1,112 @@
+/*
+ * 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<Method, List<StubContainer>> stubs = new HashMap<Method, List<StubContainer>>();
+ private Class<?> mokingClass = null;
+
+ @Override
+ public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
+ List<StubContainer> 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<StubContainer> 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
new file mode 100644
index 00000000..eec69bc0
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/DataEncrypterTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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
new file mode 100644
index 00000000..f376dd5f
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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
new file mode 100644
index 00000000..39eeadb4
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/IDMStoreTestUtil.java
@@ -0,0 +1,181 @@
+/*
+ * 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<Grant> 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<Optional<Grant>, ReadFailedException> read = mock(CheckedFuture.class);
+ DataBrokerReadMocker.getMocker(rot).addWhen("read",
+ new Object[] { LogicalDatastoreType.CONFIGURATION, grantID }, read);
+ Optional<Grant> 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<Domain> 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<Optional<Domain>, ReadFailedException> read = mock(CheckedFuture.class);
+ DataBrokerReadMocker.getMocker(rot).addWhen("read",
+ new Object[] { LogicalDatastoreType.CONFIGURATION, domainID }, read);
+ Optional<Domain> 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<Role> 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<Optional<Role>, ReadFailedException> read = mock(CheckedFuture.class);
+ DataBrokerReadMocker.getMocker(rot).addWhen("read",
+ new Object[] { LogicalDatastoreType.CONFIGURATION, roleID }, read);
+ Optional<Role> 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<User> 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<Optional<User>, ReadFailedException> read = mock(CheckedFuture.class);
+ DataBrokerReadMocker.getMocker(rot).addWhen("read",
+ new Object[] { LogicalDatastoreType.CONFIGURATION, userID }, read);
+ Optional<User> 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
new file mode 100644
index 00000000..9b7c9712
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/aaa-authn-mdsal-store-impl/src/test/java/org/opendaylight/aaa/authn/mdsal/store/MDSALConvertTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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
new file mode 100644
index 00000000..10c18790
--- /dev/null
+++ 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
@@ -0,0 +1,88 @@
+/*
+ * 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<String> roles = new ArrayList<String>();
+ 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
new file mode 100644
index 00000000..38d29147
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-mdsal-store/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>aaa-authn-mdsal-store</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>aaa-authn-mdsal-api</module>
+ <module>aaa-authn-mdsal-config</module>
+ <module>aaa-authn-mdsal-store-impl</module>
+ </modules>
+</project>
diff --git a/odl-aaa-moon/aaa-authn-sssd/pom.xml b/odl-aaa-moon/aaa-authn-sssd/pom.xml
new file mode 100644
index 00000000..b70c2466
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sssd/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-sssd</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-idpmapping</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>com.sun.jersey.jersey-test-framework</groupId>
+ <artifactId>jersey-test-framework-grizzly2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.aaa.sssd.Activator</Bundle-Activator>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..b6d5259f
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/Activator.java
@@ -0,0 +1,28 @@
+/*
+ * 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
new file mode 100644
index 00000000..0ae23b48
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sssd/src/main/java/org/opendaylight/aaa/sssd/SssdClaimAuth.java
@@ -0,0 +1,220 @@
+/*
+ * 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 &lt;jdennis@redhat.com&gt;
+ */
+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<String, Object> properties = new HashMap<String, Object>(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.
+ *
+ * <dl>
+ * <dt>ClientId</dt>
+ * <dd>A string.
+ *
+ * @see org.opendaylight.aaa.api.Claim#clientId() </dd>
+ *
+ * <dt>UserId</dt> <dd>A string.
+ * @see org.opendaylight.aaa.api.Claim#userId() </dd>
+ *
+ * <dt>User</dt> <dd>A string.
+ * @see org.opendaylight.aaa.api.Claim#user() </dd>
+ *
+ * <dt>Domain</dt> <dd>A string.
+ * @see org.opendaylight.aaa.api.Claim#domain() </dd>
+ *
+ * <dt>Roles</dt> <dd>An array of strings.
+ * @see org.opendaylight.aaa.api.Claim#roles() </dd>
+ *
+ * </dl>
+ *
+ * @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<String, Object> assertion) {
+ String assertionJson;
+ Map<String, Object> 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<String> roles = (List<String>) 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:
+ *
+ * <ul>
+ * <li>String</li>
+ * <li>Integer</li>
+ * <li>Long</li>
+ * <li>Double</li>
+ * <li>Boolean</li>
+ * <li>null</li>
+ * </ul>
+ *
+ * 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<String, Object> claim) {
+ StringWriter stringWriter = new StringWriter();
+ JsonGenerator generator = generatorFactory.createGenerator(stringWriter);
+
+ generator.writeStartObject();
+ for (Map.Entry<String, Object> 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
new file mode 100644
index 00000000..744c4df1
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-store/pom.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-store</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.aaa.store.Activator</Bundle-Activator>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/tokens.cfg</file>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
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
new file mode 100644
index 00000000..f3299723
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/Activator.java
@@ -0,0 +1,45 @@
+/*
+ * 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<String, ?> addPid(Dictionary<String, String> 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
new file mode 100644
index 00000000..df65be32
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-store/src/main/java/org/opendaylight/aaa/store/DefaultTokenStore.java
@@ -0,0 +1,154 @@
+/*
+ * 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<String, String> 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
new file mode 100644
index 00000000..b88d5c10
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 00000000..d04874f4
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-store/src/main/resources/OSGI-INF/metatype/metatype.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metatype:MetaData xmlns:metatype="http://www.osgi.org/xmlns/metatype/v1.0.0"
+ localization="OSGI-INF/metatype/metatype">
+ <OCD id="org.opendaylight.aaa.tokens" name="%org.opendaylight.aaa.tokens.name"
+ description="%org.opendaylight.aaa.tokens.description">
+ <AD id="maxCachedTokensInMemory" type="Long" default="10000"
+ name="%org.opendaylight.aaa.tokens.maxCachedTokensInMemory.name"
+ description="%org.opendaylight.aaa.tokens.maxCachedTokensInMemory.description" />
+ <AD id="maxCachedTokensOnDisk" type="Long" default="1000000"
+ name="%org.opendaylight.aaa.tokens.maxCachedTokensOnDisk.name"
+ description="%org.opendaylight.aaa.tokens.maxCachedTokensOnDisk.description" />
+ <AD id="secondsToLive" type="Long" default="3600"
+ name="%org.opendaylight.aaa.tokens.secondsToLive.name"
+ description="%org.opendaylight.aaa.tokens.secondsToLive.description" />
+ <AD id="secondsToIdle" type="Long" default="3600"
+ name="%org.opendaylight.aaa.tokens.secondsToIdle.name"
+ description="%org.opendaylight.aaa.tokens.secondsToIdle.description" />
+ </OCD>
+ <Designate pid="org.opendaylight.aaa.tokens">
+ <Object ocdref="org.opendaylight.aaa.tokens" />
+ </Designate>
+</metatype:MetaData> \ 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
new file mode 100644
index 00000000..d3dda90e
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-store/src/main/resources/tokens.cfg
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 00000000..e5c837bf
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-store/src/test/java/org/opendaylight/aaa/store/DefaultTokenStoreTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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<String, String> 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
new file mode 100644
index 00000000..25ac0fe6
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/pom.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-sts</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.common</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>com.sun.jersey.jersey-test-framework</groupId>
+ <artifactId>jersey-test-framework-grizzly2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet-tester</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ *,
+ com.sun.jersey.spi.container.servlet
+ </Import-Package>
+ <Web-ContextPath>/oauth2</Web-ContextPath>
+ <Bundle-Activator>org.opendaylight.aaa.sts.Activator</Bundle-Activator>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
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
new file mode 100644
index 00000000..1bf4591d
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/Activator.java
@@ -0,0 +1,207 @@
+/*
+ * 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<ServiceTracker<?, ?>> 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<ServiceTracker<?, ?>> servicesBuilder = new ImmutableList.Builder<ServiceTracker<?, ?>>();
+
+ // Async ServiceTrackers to track and load AAA STS bundles
+ final ServiceTracker<AuthenticationService, AuthenticationService> authenticationService = new ServiceTracker<>(
+ context, AuthenticationService.class,
+ new AAAServiceTrackerCustomizer<AuthenticationService>(
+ new Function<AuthenticationService, Void>() {
+ @Override
+ public Void apply(AuthenticationService authenticationService) {
+ ServiceLocator.getInstance().setAuthenticationService(
+ authenticationService);
+ return null;
+ }
+ }));
+ servicesBuilder.add(authenticationService);
+ authenticationService.open();
+
+ final ServiceTracker<IdMService, IdMService> idmService = new ServiceTracker<>(context,
+ IdMService.class, new AAAServiceTrackerCustomizer<IdMService>(
+ new Function<IdMService, Void>() {
+ @Override
+ public Void apply(IdMService idmService) {
+ ServiceLocator.getInstance().setIdmService(idmService);
+ return null;
+ }
+ }));
+ servicesBuilder.add(idmService);
+ idmService.open();
+
+ final ServiceTracker<TokenAuth, TokenAuth> tokenAuthService = new ServiceTracker<>(context,
+ TokenAuth.class, new AAAServiceTrackerCustomizer<TokenAuth>(
+ new Function<TokenAuth, Void>() {
+ @Override
+ public Void apply(TokenAuth tokenAuth) {
+ final List<TokenAuth> tokenAuthCollection = (List<TokenAuth>) Lists.newArrayList(tokenAuth);
+ ServiceLocator.getInstance().setTokenAuthCollection(
+ tokenAuthCollection);
+ return null;
+ }
+ }));
+ servicesBuilder.add(tokenAuthService);
+ tokenAuthService.open();
+
+ final ServiceTracker<TokenStore, TokenStore> tokenStoreService = new ServiceTracker<>(
+ context, TokenStore.class, new AAAServiceTrackerCustomizer<TokenStore>(
+ new Function<TokenStore, Void>() {
+ @Override
+ public Void apply(TokenStore tokenStore) {
+ ServiceLocator.getInstance().setTokenStore(tokenStore);
+ return null;
+ }
+ }));
+ servicesBuilder.add(tokenStoreService);
+ tokenStoreService.open();
+
+ final ServiceTracker<ClientService, ClientService> clientService = new ServiceTracker<>(
+ context, ClientService.class, new AAAServiceTrackerCustomizer<ClientService>(
+ new Function<ClientService, Void>() {
+ @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 <S>
+ */
+ static final class AAAServiceTrackerCustomizer<S> implements ServiceTrackerCustomizer<S, S> {
+
+ private Function<S, Void> callback;
+
+ public AAAServiceTrackerCustomizer(final Function<S, Void> callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public S addingService(ServiceReference<S> 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<S> reference, S service) {
+ }
+
+ @Override
+ public void removedService(ServiceReference<S> 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
new file mode 100644
index 00000000..55b5b61f
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousPasswordValidator.java
@@ -0,0 +1,30 @@
+/*
+ * 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<HttpServletRequest> {
+
+ 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
new file mode 100644
index 00000000..5b50c7da
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/AnonymousRefreshTokenValidator.java
@@ -0,0 +1,29 @@
+/*
+ * 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<HttpServletRequest> {
+
+ 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
new file mode 100644
index 00000000..2a2b34b6
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/OAuthRequest.java
@@ -0,0 +1,42 @@
+/*
+ * 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<HttpServletRequest> 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
new file mode 100644
index 00000000..2c1f84c3
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/ServiceLocator.java
@@ -0,0 +1,141 @@
+/*
+ * 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<TokenAuth> tokenAuthCollection = new Vector<>();
+
+ protected volatile CredentialAuth<PasswordCredentials> 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<PasswordCredentials> da) {
+ this.credentialAuth = da;
+ }
+
+ protected void credentialAuthAddedRemoved(CredentialAuth<PasswordCredentials> da) {
+ this.credentialAuth = null;
+ }
+
+ public List<TokenAuth> getTokenAuthCollection() {
+ return tokenAuthCollection;
+ }
+
+ public void setTokenAuthCollection(List<TokenAuth> tokenAuthCollection) {
+ this.tokenAuthCollection = tokenAuthCollection;
+ }
+
+ public CredentialAuth<PasswordCredentials> getCredentialAuth() {
+ return credentialAuth;
+ }
+
+ public synchronized void setCredentialAuth(CredentialAuth<PasswordCredentials> 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
new file mode 100644
index 00000000..3fa7a66c
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenAuthFilter.java
@@ -0,0 +1,148 @@
+/*
+ * 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 <code>AAAFilter</code> 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<String, List<String>> headers = request.getRequestHeaders();
+
+ // Go through and invoke other TokenAuth first...
+ List<TokenAuth> 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<String> 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
new file mode 100644
index 00000000..a456d702
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/main/java/org/opendaylight/aaa/sts/TokenEndpoint.java
@@ -0,0 +1,242 @@
+/*
+ * 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<String> 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
new file mode 100644
index 00000000..83a9fa51
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <servlet>
+ <servlet-name>STS</servlet-name>
+ <servlet-class>org.opendaylight.aaa.sts.TokenEndpoint</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>STS</servlet-name>
+ <url-pattern>/token</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>STS</servlet-name>
+ <url-pattern>/revoke</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>STS</servlet-name>
+ <url-pattern>/validate</url-pattern>
+ </servlet-mapping>
+</web-app>
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
new file mode 100644
index 00000000..0f806d91
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/RestFixture.java
@@ -0,0 +1,34 @@
+/*
+ * 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
new file mode 100644
index 00000000..7f888455
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenAuthTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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
new file mode 100644
index 00000000..06dd6302
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn-sts/src/test/java/org/opendaylight/aaa/sts/TokenEndpointTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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
new file mode 100644
index 00000000..06027a60
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/pom.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.aaa.Activator</Bundle-Activator>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/authn.cfg</file>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
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
new file mode 100644
index 00000000..cfe27ef0
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java
@@ -0,0 +1,51 @@
+/*
+ * 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<String, ?> addPid(Dictionary<String, String> 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
new file mode 100644
index 00000000..948cbac6
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java
@@ -0,0 +1,122 @@
+/*
+ * 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<String> 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
new file mode 100644
index 00000000..5f6420a3
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java
@@ -0,0 +1,77 @@
+/*
+ * 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<String, String> 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<Authentication> 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<String, ?> 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
new file mode 100644
index 00000000..4e4a8ef3
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java
@@ -0,0 +1,160 @@
+/*
+ * 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<String> 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<String> 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<String> roles;
+
+ protected ImmutableClaim(ClaimBuilder base) {
+ clientId = base.clientId;
+ userId = base.userId;
+ user = base.user;
+ domain = base.domain;
+ roles = ImmutableSet.<String> 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<String> 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
new file mode 100644
index 00000000..e7e51424
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java
@@ -0,0 +1,88 @@
+/*
+ * 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 <client_id>:<client_secret>";
+ private static final String UNAUTHORIZED_CLIENT_ERR = "Unauthorized client";
+
+ // Defaults (needed only for non-Karaf deployments)
+ static final Dictionary<String, String> defaults = new Hashtable<>();
+ static {
+ defaults.put(CLIENTS, "dlux:secrete");
+ }
+
+ private final Map<String, String> 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<String, ?> 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<String, String> 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
new file mode 100644
index 00000000..17204d0e
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ * <p>
+ *
+ * <em>Arrays are not handled by this class</em>. This is because the
+ * <code>Arrays.equals</code> 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
new file mode 100644
index 00000000..c295b3ed
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java
@@ -0,0 +1,104 @@
+/*****************************************************************************
+ * 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 <tt>hashCode</tt>.
+ *
+ * Example use case:
+ *
+ * <pre>
+ * 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;
+ * }
+ * </pre>
+ */
+public final class HashCodeUtil {
+
+ /**
+ * An initial value for a <tt>hashCode</tt>, to which is added contributions
+ * from fields. Using a non-zero value decreases collisions of
+ * <tt>hashCode</tt> 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));
+ }
+
+ /**
+ * <tt>aObject</tt> is a possibly-null object field, and possibly an array.
+ *
+ * If <tt>aObject</tt> 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
new file mode 100644
index 00000000..d8a2e87a
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java
@@ -0,0 +1,87 @@
+/*
+ * 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
new file mode 100644
index 00000000..3ded52da
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java
@@ -0,0 +1,258 @@
+/*
+ * 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 <T>
+ * queue element type
+ */
+public class SecureBlockingQueue<T> implements BlockingQueue<T> {
+ private final BlockingQueue<SecureData<T>> queue;
+
+ /**
+ * Constructor.
+ *
+ * @param queue
+ * blocking queue implementation to use
+ */
+ public SecureBlockingQueue(BlockingQueue<SecureData<T>> 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<T> iterator() {
+ return new Iterator<T>() {
+ Iterator<SecureData<T>> 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> T[] toArray(T[] a) {
+ return toData().toArray(a);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return toData().containsAll(c);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends T> 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<T>(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<SecureData<T>> it = queue.iterator();
+ while (it.hasNext()) {
+ SecureData<T> sd = it.next();
+ if (sd.data.equals(o)) {
+ return queue.remove(sd);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ Iterator<SecureData<T>> it = queue.iterator();
+ while (it.hasNext()) {
+ SecureData<T> sd = it.next();
+ if (sd.data.equals(o)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int drainTo(Collection<? super T> c) {
+ Collection<SecureData<T>> sd = new ArrayList<>();
+ int n = queue.drainTo(sd);
+ c.addAll(toData(sd));
+ return n;
+ }
+
+ @Override
+ public int drainTo(Collection<? super T> c, int maxElements) {
+ Collection<SecureData<T>> sd = new ArrayList<>();
+ int n = queue.drainTo(sd, maxElements);
+ c.addAll(toData(sd));
+ return n;
+ }
+
+ // Rehydrate security context
+ private T setAuth(SecureData<T> i) {
+ AuthenticationManager.instance().set(i.auth);
+ return i.data;
+ }
+
+ // Construct secure data collection from a plain old data collection
+ @SuppressWarnings("unchecked")
+ private Collection<SecureData<T>> fromData(Collection<?> c) {
+ Collection<SecureData<T>> sd = new ArrayList<>(c.size());
+ for (Object d : c) {
+ sd.add((SecureData<T>) new SecureData<>(d));
+ }
+ return sd;
+ }
+
+ // Extract the data portion out from the secure data
+ @SuppressWarnings("unchecked")
+ private Collection<T> toData() {
+ return toData(Arrays.<SecureData<T>> asList(queue.toArray(new SecureData[0])));
+ }
+
+ // Extract the data portion out from the secure data
+ private Collection<T> toData(Collection<SecureData<T>> secureData) {
+ Collection<T> data = new ArrayList<>(secureData.size());
+ Iterator<SecureData<T>> it = secureData.iterator();
+ while (it.hasNext()) {
+ data.add(it.next().data);
+ }
+ return data;
+ }
+
+ // Inject security context
+ public static final class SecureData<T> {
+ 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
new file mode 100644
index 00000000..75537f6b
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,12 @@
+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 <client_id:client_secret>
+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
new file mode 100644
index 00000000..10150587
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metatype:MetaData xmlns:metatype="http://www.osgi.org/xmlns/metatype/v1.0.0"
+ localization="OSGI-INF/metatype/metatype">
+ <OCD id="org.opendaylight.aaa.authn" name="%org.opendaylight.aaa.authn.name"
+ description="%org.opendaylight.aaa.authn.description">
+ <AD id="authorizedClients" type="String" default="dlux:secrete"
+ name="%org.opendaylight.aaa.authn.authorizedClients.name"
+ description="%org.opendaylight.aaa.authn.authorizedClients.description" />
+ <AD id="authEnabled" type="String" default="true"
+ name="%org.opendaylight.aaa.authn.authEnabled.name"
+ description="%org.opendaylight.aaa.authn.authEnabled.description" />
+ </OCD>
+ <Designate pid="org.opendaylight.aaa.authn">
+ <Object ocdref="org.opendaylight.aaa.authn" />
+ </Designate>
+</metatype:MetaData> \ 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
new file mode 100644
index 00000000..e7326f86
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg
@@ -0,0 +1,2 @@
+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
new file mode 100644
index 00000000..2f69fe5b
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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<String> 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
new file mode 100644
index 00000000..540df287
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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<Authentication> 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<Future<Authentication>> fs = Executors.newFixedThreadPool(2).invokeAll(
+ Arrays.asList(new Worker(), new Worker()));
+ for (Future<Authentication> f : fs) {
+ assertEquals(auth, f.get());
+ }
+
+ as.clear();
+ fs = Executors.newFixedThreadPool(2).invokeAll(Arrays.asList(new Worker(), new Worker()));
+ for (Future<Authentication> f : fs) {
+ assertNull(f.get());
+ }
+ }
+
+ @Test
+ public void testUpdatedValid() throws ConfigurationException {
+ Dictionary<String, String> 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<String, String> 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<String, String> props = new Hashtable<>();
+
+ props.put("Invalid Key", "true");
+ as.updated(props);
+ }
+
+ private class Worker implements Callable<Authentication> {
+ @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
new file mode 100644
index 00000000..372eb6d2
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java
@@ -0,0 +1,208 @@
+/*
+ * 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
new file mode 100644
index 00000000..059ba9a3
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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<String, String> 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<String, String> 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
new file mode 100644
index 00000000..2dabb77b
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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<PasswordCredentials> 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
new file mode 100644
index 00000000..16627d9f
--- /dev/null
+++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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<Runnable> queue = new SecureBlockingQueue<>(
+ new ArrayBlockingQueue<SecureData<Runnable>>(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<Runnable> queue = new ArrayBlockingQueue<Runnable>(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<String> queue = new SecureBlockingQueue<>(
+ new ArrayBlockingQueue<SecureData<String>>(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<String> queue = new SecureBlockingQueue<>(
+ new ArrayBlockingQueue<SecureData<String>>(6));
+ for (int i = 1; i <= 3; i++)
+ queue.add("User" + i);
+ Iterator<String> it = queue.iterator();
+ while (it.hasNext())
+ assertTrue(it.next().startsWith("User"));
+ assertEquals(3, queue.toArray().length);
+ List<String> 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<String> queue = new SecureBlockingQueue<>(
+ new ArrayBlockingQueue<SecureData<String>>(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<String> 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<Authentication> {
+ 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<String> {
+ private final String name;
+ private final String userId;
+ private final String role;
+ private final BlockingQueue<String> queue;
+
+ Producer(String name, String userId, String role, BlockingQueue<String> 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<String> {
+ private final BlockingQueue<String> queue;
+
+ Consumer(BlockingQueue<String> 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
new file mode 100644
index 00000000..4e19ed42
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-config/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <artifactId>authz-service-config</artifactId>
+ <description>AuthZ Service Configuration files </description>
+ <packaging>jar</packaging>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/initial/${config.authz.service.configfile}</file>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
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
new file mode 100644
index 00000000..5b59ca20
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-config/src/main/resources/initial/08-authz-config.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2013 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
+-->
+<snapshot>
+ <configuration>
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+
+ <!-- defines an implementation module -->
+ <module>
+ <type xmlns:authz="urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv">authz:aaa-authz-service</type>
+ <name>aaa-authz-service</name>
+
+ <dom-broker>
+ <type xmlns:dom="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">dom:dom-broker-osgi-registry</type>
+ <name>dom-broker</name>
+ </dom-broker>
+
+ <data-broker>
+ <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-data-broker</type>
+ <name>binding-data-broker</name>
+ </data-broker>
+
+ <policies xmlns="urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv">
+ <service xmlns="urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv">RestConfService</service>
+ <action xmlns="urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv">Any</action>
+ <resource xmlns="urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv">*</resource>
+ <role xmlns="urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv">admin</role>
+ </policies>
+
+ </module>
+ </modules>
+
+ <services xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+ <service>
+ <type xmlns:dom="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">dom:dom-broker-osgi-registry</type>
+ <instance>
+ <name>authz-connector-default</name>
+ <provider>
+ /modules/module[type='aaa-authz-service'][name='aaa-authz-service']
+ </provider>
+ </instance>
+ </service>
+ </services>
+
+ </data>
+
+
+ </configuration>
+ <required-capabilities>
+ <capability>urn:opendaylight:params:xml:ns:yang:controller:config:aaa-authz:srv?module=aaa-authz-service-impl&amp;revision=2014-07-01</capability>
+ </required-capabilities>
+
+</snapshot>
diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml b/odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml
new file mode 100644
index 00000000..a1d3a28f
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-model/pom.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authz-model</artifactId>
+ <name>${project.artifactId}</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>yang-binding</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>ietf-inet-types</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>ietf-yang-types</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>yang-ext</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <stylesheet>maven</stylesheet>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>aggregate</goal>
+ </goals>
+ <phase>site</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>generate-sources</goal>
+ </goals>
+ <configuration>
+ <yangFilesRootDir>src/main/yang</yangFilesRootDir>
+ <codeGenerators>
+ <generator>
+ <codeGeneratorClass>
+ org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl
+ </codeGeneratorClass>
+ <outputBaseDir>${salGeneratorPath}</outputBaseDir>
+ </generator>
+ </codeGenerators>
+ <inspectDependencies>true</inspectDependencies>
+ </configuration>
+ </execution>
+ </executions>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>maven-sal-api-gen-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <type>jar</type>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+ <packaging>bundle</packaging>
+
+</project>
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
new file mode 100644
index 00000000..2e0cf9cb
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-model/src/main/yang/authorization-schema.yang
@@ -0,0 +1,190 @@
+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
new file mode 100644
index 00000000..95db7458
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <artifactId>authz-restconf-config</artifactId>
+
+ <description>AuthZ Restconf Connector Configuration file </description>
+ <packaging>jar</packaging>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/initial/${config.restconf.configfile}</file>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..deba6558
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-restconf-config/src/main/resources/initial/09-rest-connector.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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
+-->
+<snapshot>
+ <configuration>
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+
+ <module>
+ <type xmlns:rest="urn:opendaylight:params:xml:ns:yang:controller:md:sal:rest:connector">rest:rest-connector-impl</type>
+ <name>rest-connector-default-impl</name>
+ <websocket-port>8185</websocket-port>
+ <dom-broker>
+ <type xmlns:dom="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">dom:dom-broker-osgi-registry</type>
+ <name>authz-connector-default</name>
+ </dom-broker>
+ </module>
+ </modules>
+
+ <services xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+ <service>
+ <type xmlns:rest="urn:opendaylight:params:xml:ns:yang:controller:md:sal:rest:connector">rest:rest-connector</type>
+ <instance>
+ <name>rest-connector-default</name>
+ <provider>
+ /modules/module[type='rest-connector-impl'][name='rest-connector-default-impl']
+ </provider>
+ </instance>
+ </service>
+ </services>
+
+ </data>
+ </configuration>
+ <required-capabilities>
+ <capability>urn:opendaylight:params:xml:ns:yang:controller:md:sal:rest:connector?module=opendaylight-rest-connector&amp;revision=2014-07-24</capability>
+ </required-capabilities>
+</snapshot>
diff --git a/odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml b/odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml
new file mode 100644
index 00000000..a0afef82
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/pom.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ 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 -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>aaa-authz-service</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-data-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authz-model</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-core-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-core-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>jaxrs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <!-- <Bundle-Activator>org.opendaylight.aaa.authz.srv.AuthzProvider</Bundle-Activator> -->
+ <Export-Package>org.opendaylight.aaa.config.yang.aaa_srv,</Export-Package>
+ </instructions>
+ </configuration>
+ <!-- <configuration> <Export-Package> </Export-Package> </configuration> -->
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <executions>
+ <execution>
+ <id>config</id>
+ <goals>
+ <goal>generate-sources</goal>
+ </goals>
+ <configuration>
+ <codeGenerators>
+ <generator>
+ <codeGeneratorClass>
+ org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ </codeGeneratorClass>
+ <outputBaseDir>${jmxGeneratorPath}</outputBaseDir>
+ <additionalConfiguration>
+ <namespaceToPackage1>
+ urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang
+ </namespaceToPackage1>
+ </additionalConfiguration>
+ </generator>
+ <generator>
+ <codeGeneratorClass>org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl</codeGeneratorClass>
+ <outputBaseDir>${salGeneratorPath}</outputBaseDir>
+ </generator>
+ </codeGenerators>
+ <inspectDependencies>true</inspectDependencies>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>yang-jmx-generator-plugin</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>maven-sal-api-gen-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
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
new file mode 100644
index 00000000..d4ac79af
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzBrokerImpl.java
@@ -0,0 +1,150 @@
+/*
+ * 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<ProviderFunctionality> 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<ProviderFunctionality> 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<ConsumerFunctionality> 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
new file mode 100644
index 00000000..07ba51cd
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImpl.java
@@ -0,0 +1,46 @@
+/*
+ * 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 extends BrokerService> T getService(Class<T> 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
new file mode 100644
index 00000000..4cc232bc
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDataReadWriteTransaction.java
@@ -0,0 +1,129 @@
+/*
+ * 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<Void, TransactionCommitFailedException> submit() {
+ if (AuthzServiceImpl.isAuthorized(ActionType.Submit)) {
+ return domDataReadWriteTransaction.submit();
+ }
+ TransactionCommitFailedException e = new TransactionCommitFailedException(
+ "Unauthorized User");
+ return Futures.immediateFailedCheckedFuture(e);
+ }
+
+ @Deprecated
+ @Override
+ public ListenableFuture<RpcResult<TransactionStatus>> commit() {
+ if (AuthzServiceImpl.isAuthorized(ActionType.Commit)) {
+ return domDataReadWriteTransaction.commit();
+ }
+ TransactionCommitFailedException e = new TransactionCommitFailedException(
+ "Unauthorized User");
+ return Futures.immediateFailedCheckedFuture(e);
+ }
+
+ @Override
+ public CheckedFuture<Optional<NormalizedNode<?, ?>>, 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<Boolean, ReadFailedException> 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
new file mode 100644
index 00000000..911f5a48
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzDomDataBroker.java
@@ -0,0 +1,100 @@
+/*
+ * 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<Class<? extends DOMDataBrokerExtension>, 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<DOMDataChangeListener> 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
new file mode 100644
index 00000000..dbfea6ed
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzProviderContextImpl.java
@@ -0,0 +1,47 @@
+/*
+ * 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 extends BrokerService> T getService(Class<T> 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
new file mode 100644
index 00000000..c46ffe7c
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzReadOnlyTransaction.java
@@ -0,0 +1,69 @@
+/*
+ * 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<Optional<NormalizedNode<?, ?>>, 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<Boolean, ReadFailedException> 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
new file mode 100644
index 00000000..fb344812
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzServiceImpl.java
@@ -0,0 +1,121 @@
+/*
+ * 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<Policies> 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> 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
new file mode 100644
index 00000000..1123b928
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/aaa/authz/srv/AuthzWriteOnlyTransaction.java
@@ -0,0 +1,103 @@
+/*
+ * 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<Void, TransactionCommitFailedException> submit() {
+ if (AuthzServiceImpl.isAuthorized(ActionType.Submit)) {
+ return domDataWriteTransaction.submit();
+ }
+ TransactionCommitFailedException e = new TransactionCommitFailedException(
+ "Unauthorized User");
+ return Futures.immediateFailedCheckedFuture(e);
+ }
+
+ @Deprecated
+ @Override
+ public ListenableFuture<RpcResult<TransactionStatus>> 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
new file mode 100644
index 00000000..a590b982
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModule.java
@@ -0,0 +1,76 @@
+/*
+ * 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<AuthenticationService> 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
new file mode 100644
index 00000000..3ff67f54
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/java/org/opendaylight/controller/config/yang/config/aaa_authz/srv/AuthzSrvModuleFactory.java
@@ -0,0 +1,53 @@
+/*
+ * 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
new file mode 100644
index 00000000..954d0480
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/main/yang/aaa-authz-service-impl.yang
@@ -0,0 +1,115 @@
+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
new file mode 100644
index 00000000..fb033341
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/aaa-authz-service/src/test/java/org/opendaylight/aaa/authz/srv/AuthzConsumerContextImplTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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
new file mode 100644
index 00000000..bdc1852f
--- /dev/null
+++ b/odl-aaa-moon/aaa-authz/pom.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authz</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>aaa-authz-model</module>
+ <module>aaa-authz-service</module>
+ <module>aaa-authz-config</module>
+ <module>aaa-authz-restconf-config</module>
+ </modules>
+</project>
diff --git a/odl-aaa-moon/aaa-credential-store-api/pom.xml b/odl-aaa-moon/aaa-credential-store-api/pom.xml
new file mode 100644
index 00000000..e7dfb81c
--- /dev/null
+++ b/odl-aaa-moon/aaa-credential-store-api/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+(c) Copyright 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>binding-parent</artifactId>
+ <version>0.8.1-Beryllium-SR1</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-credential-store-api</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>bundle</packaging>
+</project>
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
new file mode 100644
index 00000000..7d1f55a3
--- /dev/null
+++ b/odl-aaa-moon/aaa-credential-store-api/src/main/yang/credential-model.yang
@@ -0,0 +1,47 @@
+/*
+ * 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
new file mode 100644
index 00000000..1dd33310
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/.gitignore
@@ -0,0 +1,2 @@
+/target/
+/target/
diff --git a/odl-aaa-moon/aaa-h2-store/pom.xml b/odl-aaa-moon/aaa-h2-store/pom.xml
new file mode 100644
index 00000000..2b31525c
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/pom.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-h2-store</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-api</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- JDBC -->
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${bundle.plugin.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>com.google.*,org.opendaylight.aaa.api.*,org.apache.felix.*,org.slf4j.*,org.opendaylight.*,org.osgi.*,org.apache.commons.lang3</Import-Package>
+ <Private-Package>org.h2.*</Private-Package>
+ <Embed-Dependency>h2</Embed-Dependency>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <executions>
+ <execution>
+ <id>config</id>
+ <goals>
+ <goal>generate-sources</goal>
+ </goals>
+ <configuration>
+ <codeGenerators>
+ <generator>
+ <codeGeneratorClass>org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator</codeGeneratorClass>
+ <outputBaseDir>${jmxGeneratorPath}</outputBaseDir>
+ <additionalConfiguration>
+ <namespaceToPackage1>urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang</namespaceToPackage1>
+ </additionalConfiguration>
+ </generator>
+ <generator>
+ <codeGeneratorClass>org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl</codeGeneratorClass>
+ <outputBaseDir>${salGeneratorPath}</outputBaseDir>
+ </generator>
+ </codeGenerators>
+ <inspectDependencies>true</inspectDependencies>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>maven-sal-api-gen-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>yang-jmx-generator-plugin</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/initial/08-aaa-h2-store-config.xml</file>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..a35ca48f
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/config/IdmLightConfig.java
@@ -0,0 +1,133 @@
+/*
+ * 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
new file mode 100644
index 00000000..ba00eb84
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/AbstractStore.java
@@ -0,0 +1,187 @@
+/*
+ * 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<T> {
+ /**
+ * 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<T> listAll() throws StoreException {
+ List<T> 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<T> listFromStatement(PreparedStatement ps) throws StoreException {
+ List<T> 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 <b>not</b> 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
new file mode 100644
index 00000000..aa8f4b30
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/DomainStore.java
@@ -0,0 +1,166 @@
+/*
+ * 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<Domain> {
+ 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
new file mode 100644
index 00000000..ee86e0ba
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/GrantStore.java
@@ -0,0 +1,158 @@
+/*
+ * 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<Grant> {
+ 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
new file mode 100644
index 00000000..da40a17b
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/H2Store.java
@@ -0,0 +1,316 @@
+/*
+ * 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
new file mode 100644
index 00000000..e7defa4a
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/RoleStore.java
@@ -0,0 +1,151 @@
+/*
+ * 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<Role> {
+ 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
new file mode 100644
index 00000000..7d2f2b9a
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/StoreException.java
@@ -0,0 +1,29 @@
+/*
+ * 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
new file mode 100644
index 00000000..96b8013f
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/UserStore.java
@@ -0,0 +1,202 @@
+/*
+ * 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<User> {
+ 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
new file mode 100644
index 00000000..fe7dd2a6
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModule.java
@@ -0,0 +1,49 @@
+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
new file mode 100644
index 00000000..dc9e7f99
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/h2/store/rev151128/AAAH2StoreModuleFactory.java
@@ -0,0 +1,29 @@
+/*
+* 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
new file mode 100644
index 00000000..cfe60812
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/resources/initial/08-aaa-h2-store-config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ 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
+-->
+<snapshot>
+ <configuration>
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+ <module>
+ <type xmlns:authn="config:aaa:authn:h2:store">authn:aaa-h2-store</type>
+ <name>aaa-h2-store</name>
+ </module>
+ </modules>
+ </data>
+ </configuration>
+ <required-capabilities>
+ <capability>config:aaa:authn:h2:store?module=aaa-h2-store&amp;revision=2015-11-28</capability>
+ </required-capabilities>
+
+</snapshot>
+
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
new file mode 100644
index 00000000..af2d9bdc
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/main/yang/aaa-h2-store.yang
@@ -0,0 +1,28 @@
+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
new file mode 100644
index 00000000..f11a99eb
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/DomainStoreTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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
new file mode 100644
index 00000000..168b67e2
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/GrantStoreTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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
new file mode 100644
index 00000000..f583a302
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/H2StoreTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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
new file mode 100644
index 00000000..37cb17a6
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/RoleStoreTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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
new file mode 100644
index 00000000..e214c261
--- /dev/null
+++ b/odl-aaa-moon/aaa-h2-store/src/test/java/org/opendaylight/aaa/h2/persistence/UserStoreTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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
new file mode 100644
index 00000000..5a86b0d1
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/pom.xml
@@ -0,0 +1,229 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-idmlight</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <!--Yang Binding -->
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-api</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+
+ <!-- JSON JAXB Stuff -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-json-org</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-base</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-json-provider</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-jaxb-annotations</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlets</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>com.sun.jersey.jersey-test-framework</groupId>
+ <artifactId>jersey-test-framework-grizzly2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <executions>
+ <execution>
+ <id>config</id>
+ <goals>
+ <goal>generate-sources</goal>
+ </goals>
+ <configuration>
+ <codeGenerators>
+ <generator>
+ <codeGeneratorClass>org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator</codeGeneratorClass>
+ <outputBaseDir>${jmxGeneratorPath}</outputBaseDir>
+ <additionalConfiguration>
+ <namespaceToPackage1>urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang</namespaceToPackage1>
+ </additionalConfiguration>
+ </generator>
+ <generator>
+ <codeGeneratorClass>org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl</codeGeneratorClass>
+ <outputBaseDir>${salGeneratorPath}</outputBaseDir>
+ </generator>
+ </codeGenerators>
+ <inspectDependencies>true</inspectDependencies>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>maven-sal-api-gen-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>yang-jmx-generator-plugin</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/initial/08-aaa-idmlight-config.xml</file>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ <execution>
+ <id>attach-artifacts-idmtool</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/idmtool.py</file>
+ <type>py</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <!-- override default version so we don't use bnd 2.3.0 when embedding sqlite -->
+
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>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</Import-Package>
+ <Web-ContextPath>/auth</Web-ContextPath>
+ <!--<Web-Connectors>adminConn</Web-Connectors> -->
+ <!--Bundle-Activator>org.opendaylight.aaa.idm.Activator</Bundle-Activator-->
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
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
new file mode 100644
index 00000000..6fcba5d6
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java
@@ -0,0 +1,57 @@
+/*
+ * 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:
+ * <code>http://{HOST}:{PORT}/auth/v1/</code>
+ *
+ * For example, the users REST endpoint is:
+ * <code>http://{HOST}:{PORT}/auth/v1/users</code>
+ *
+ * This application is responsible for interaction with the backing h2
+ * database store.
+ *
+ * @author liemmn
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ * @see <code>org.opendaylight.aaa.idm.rest.DomainHandler</code>
+ * @see <code>org.opendaylight.aaa.idm.rest.UserHandler</code>
+ * @see <code>org.opendaylight.aaa.idm.rest.RoleHandler</code>
+ */
+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<Class<?>> getClasses() {
+ return new HashSet<Class<?>>(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
new file mode 100644
index 00000000..d17d2b13
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java
@@ -0,0 +1,208 @@
+/*
+ * 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<PasswordCredentials>, 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
+ * <code>PasswordCredentials</code>.
+ */
+ private static Map<String, Map<PasswordCredentials, Claim>> claimCache = new ConcurrentHashMap<>();
+
+ // adds a store for the default "sdn" domain
+ static {
+ claimCache.put(IIDMStore.DEFAULT_DOMAIN,
+ new ConcurrentHashMap<PasswordCredentials, Claim>());
+ }
+
+ @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<PasswordCredentials, Claim> cache = claimCache.get(domain);
+ if (cache == null) {
+ cache = new ConcurrentHashMap<PasswordCredentials, Claim>();
+ 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<PasswordCredentials, Claim> 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<User> 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<String> roles = new ArrayList<String>();
+ Grants grants = AAAIDMLightModule.getStore().getGrants(domain.getDomainid(),
+ user.getUserid());
+ List<Grant> 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<String> listDomains(String userId) {
+ LOG.debug("list Domains for userId: {}", userId);
+ List<String> domains = new ArrayList<String>();
+ try {
+ Grants grants = AAAIDMLightModule.getStore().getGrants(userId);
+ List<Grant> 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<String> listRoles(String userId, String domainName) {
+ LOG.debug("listRoles");
+ List<String> roles = new ArrayList<String>();
+
+ 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<Grant> 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
new file mode 100644
index 00000000..111665c6
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java
@@ -0,0 +1,118 @@
+/*
+ * 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
+ * <code>AAAIDMLightModule.createInstance()</code>. StoreBuilder is responsible
+ * for initializing the H2 database with initial default user account
+ * information. By default, the following users are created:
+ * <ol>
+ * <li>admin</li>
+ * <li>user</li>
+ * </ol>
+ *
+ * By default, the following domain is created:
+ * <ol>
+ * <li>sdn</li>
+ * </ol>
+ *
+ * By default, the following grants are created:
+ * <ol>
+ * <li>admin with admin role on sdn</li>
+ * <li>admin with user role on sdn</li>
+ * <li>user with user role on sdn</li>
+ * </ol>
+ *
+ * @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
new file mode 100644
index 00000000..7ddc0748
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java
@@ -0,0 +1,591 @@
+/*
+ * 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 <code>/auth/v1/domains</code>.
+ *
+ * The following provides examples of curl commands and payloads to utilize the
+ * domains REST endpoint:
+ *
+ * <b>Get All Domains</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/domains</code>
+ *
+ * <b>Get A Specific Domain</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/domains/{id}</code>
+ *
+ * <b>Create A Domain</b>
+ * <code>curl -u admin:admin -X POST -H "Content-Type: application/json" --data-binary {@literal @}domain.json http://{HOST}:{PORT}/auth/v1/domains</code>
+ * Example domain.json <code>{
+ * "description": "new domain",
+ * "enabled", "true",
+ * "name", "not sdn"
+ * }</code>
+ *
+ * <b>Update A Domain</b>
+ * <code>curl -u admin:admin -X PUT -H "Content-Type: application/json" --data-binary {@literal @}domain.json http://{HOST}:{PORT}/auth/v1/domains</code>
+ * Example domain.json <code>{
+ * "description": "new domain description",
+ * "enabled", "true",
+ * "name", "not sdn"
+ * }</code>
+ *
+ * @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 <code>domainId</code>.
+ *
+ * @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:
+ * <code>enabled</code>: <code>false</code>
+ * <code>description</code>: An empty string (<code>""</code>).
+ *
+ * @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<Role> roleList = new ArrayList<Role>();
+
+ 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<User> 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<Grant> 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<Role> roleList = new ArrayList<Role>();
+
+ 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<Grant> 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
new file mode 100644
index 00000000..34a60c0c
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java
@@ -0,0 +1,228 @@
+/*
+ * 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 <code>/auth/v1/roles</code>.
+ *
+ * The following provides examples of curl commands and payloads to utilize the
+ * roles REST endpoint:
+ *
+ * <b>Get All Roles</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/roles</code>
+ *
+ * <b>Get A Specific Role</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/roles/{id}</code>
+ *
+ * <b>Create A Role</b>
+ * <code>curl -u admin:admin -X POST -H "Content-Type: application/json" --data-binary {@literal @}role.json http://{HOST}:{PORT}/auth/v1/roles</code>
+ * An example of role.json:
+ * <code>{
+ * "name":"IT Administrator",
+ * "description":"A user role for IT admins"
+ * }</code>
+ *
+ * <b>Update A Role</b>
+ * <code>curl -u admin:admin -X PUT -H "Content-Type: application/json" --data-binary {@literal @}role.json http://{HOST}:{PORT}/auth/v1/roles/{id}</code>
+ * An example of role.json:
+ * <code>{
+ * "name":"IT Administrator Limited",
+ * "description":"A user role for IT admins who can only do one thing"
+ * }</code>
+ *
+ * @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 <code>id</code>
+ *
+ * @param id the String id for the role
+ * @return A response with the role identified by <code>id</code>, 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 <code>id</code>.
+ *
+ * @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
new file mode 100644
index 00000000..24fefd7b
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java
@@ -0,0 +1,419 @@
+/*
+ * 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 <code>/auth/v1/users</code>.
+ *
+ * The following provides examples of how curl commands and payloads to utilize
+ * the users REST endpoint:
+ *
+ * <b>Get All Users</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/users</code>
+ *
+ * <b>Get A Specific User</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/users/{id}</code>
+ *
+ * <b>Create A User</b>
+ * <code>curl -u admin:admin -X POST -H "Content-type: application/json" --data-binary {@literal @}user.json http://{HOST}:{PORT}/auth/v1/users</code>
+ * An example of user.json file is:
+ * <code>{
+ * "name": "admin2",
+ * "password", "admin2",
+ * "domain": "sdn"
+ * }</code>
+ *
+ * <b>Update A User</b>
+ * <code>curl -u admin:admin -X PUT -H "Content-type: application/json" --data-binary {@literal @}user.json http://{HOST}:{PORT}/auth/v1/users/{id}</code>
+ * An example of user.json file is:
+ * <code>{
+ * "name": "admin2",
+ * "password", "admin2",
+ * "domain": "sdn",
+ * "description", "Simple description."
+ * }</code>
+ *
+ * <b>Delete A User</b>
+ * <code>curl -u admin:admin -X DELETE http://{HOST}:{PORT}/auth/v1/users/{id}</code>
+ *
+ * @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 <code>/auth/v1/users</code> 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 <code>/auth/v1/users</code>, the
+ * password field is replaced with <code>REDACTED_PASSWORD</code> for
+ * security reasons.
+ */
+ private static final String REDACTED_PASSWORD = "**********";
+
+ /**
+ * When an HTTP GET is performed on <code>/auth/v1/users</code>, the salt
+ * field is replaced with <code>REDACTED_SALT</code> 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<User> 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 <code>id</code>. 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:
+ * <code>description</code>: An empty string (<code>""</code>).
+ * <code>email</code>: An empty string (<code>""</code>).
+ * <code>password</code>: <code>changeme</code> <code>enabled</code>:
+ * <code>true</code>
+ *
+ * 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 <code>Response</code> 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 <code>Response</code> 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 <code>Response</code> 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 <code>fieldName</code>
+ * @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 <code>fieldName</code>
+ * @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 <code>user</code> 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
new file mode 100644
index 00000000..f865162a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java
@@ -0,0 +1,46 @@
+/*
+ * 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
new file mode 100644
index 00000000..d6872635
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java
@@ -0,0 +1,90 @@
+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<IIDMStore, IIDMStore> storeServiceTracker = new ServiceTracker<>(bundleContext, IIDMStore.class,
+ new ServiceTrackerCustomizer<IIDMStore, IIDMStore>() {
+ @Override
+ public IIDMStore addingService(ServiceReference<IIDMStore> 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<IIDMStore> reference, IIDMStore service) {
+ }
+
+ @Override
+ public void removedService(ServiceReference<IIDMStore> 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
new file mode 100644
index 00000000..de277da8
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java
@@ -0,0 +1,29 @@
+/*
+* 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
new file mode 100644
index 00000000..9a19155a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <servlet>
+ <servlet-name>IdmLight</servlet-name>
+ <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.opendaylight.aaa.idm.IdmLightApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name><param-value>true</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>IdmLight</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <!-- Shiro Filter -->
+ <context-param>
+ <param-name>shiroEnvironmentClass</param-name>
+ <param-value>org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment</param-value>
+ </context-param>
+
+ <listener>
+ <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
+ </listener>
+
+ <filter>
+ <filter-name>ShiroFilter</filter-name>
+ <filter-class>org.opendaylight.aaa.shiro.filters.AAAFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>ShiroFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <filter>
+ <filter-name>cross-origin-restconf</filter-name>
+ <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
+ <init-param>
+ <param-name>allowedOrigins</param-name>
+ <param-value>*</param-value>
+ </init-param>
+ <init-param>
+ <param-name>allowedMethods</param-name>
+ <param-value>GET,POST,OPTIONS,DELETE,PUT,HEAD</param-value>
+ </init-param>
+ <init-param>
+ <param-name>allowedHeaders</param-name>
+ <param-value>origin, content-type, accept, authorization, Authorization</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>cross-origin-restconf</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>NB api</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ <http-method>POST</http-method>
+ <http-method>GET</http-method>
+ <http-method>PUT</http-method>
+ <http-method>PATCH</http-method>
+ <http-method>DELETE</http-method>
+ <http-method>HEAD</http-method>
+ </web-resource-collection>
+ </security-constraint>
+
+</web-app> \ 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
new file mode 100644
index 00000000..d0a31ba2
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py
@@ -0,0 +1,247 @@
+#!/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
new file mode 100644
index 00000000..695ce762
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ 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
+-->
+<snapshot>
+ <configuration>
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+ <module>
+ <type xmlns:authn="config:aaa:authn:idmlight">authn:aaa-idmlight</type>
+ <name>aaa-idmlight</name>
+ </module>
+ </modules>
+ </data>
+ </configuration>
+ <required-capabilities>
+ <capability>config:aaa:authn:idmlight?module=aaa-idmlight&amp;revision=2015-12-04</capability>
+ </required-capabilities>
+
+</snapshot>
+
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
new file mode 100644
index 00000000..4f28d755
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang
@@ -0,0 +1,28 @@
+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
new file mode 100644
index 00000000..44fadf7a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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<User> lu = new LinkedList<>();
+ lu.add(user);
+ users.setUsers(lu);
+
+ Grants grants = new Grants();
+ Grant grant = new Grant();
+ List<Grant> 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
new file mode 100644
index 00000000..6385b48d
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/cleardb.sh
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 00000000..4dfd25e9
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/domain.json
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000..69244b30
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/domain2.json
@@ -0,0 +1,5 @@
+{
+ "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
new file mode 100644
index 00000000..0c4a9e90
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/grant.json
@@ -0,0 +1,4 @@
+{
+ "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
new file mode 100644
index 00000000..ad685b7a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/grant2.json
@@ -0,0 +1,4 @@
+{
+ "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
new file mode 100644
index 00000000..a3dd995d
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/result.json
@@ -0,0 +1 @@
+{"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
new file mode 100644
index 00000000..cf93caae
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/role-admin.json
@@ -0,0 +1,4 @@
+{
+ "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
new file mode 100644
index 00000000..78588c9a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/role-user.json
@@ -0,0 +1,4 @@
+{
+ "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
new file mode 100644
index 00000000..3589be58
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/test.sh
@@ -0,0 +1,308 @@
+# 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
new file mode 100644
index 00000000..6f30d705
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/user.json
@@ -0,0 +1,7 @@
+{
+ "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
new file mode 100644
index 00000000..9864cdb2
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/user2.json
@@ -0,0 +1,7 @@
+{
+ "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
new file mode 100644
index 00000000..e5258b98
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/userpwd.json
@@ -0,0 +1,4 @@
+{
+ "username":"peter",
+ "userpwd":"foobar"
+}
diff --git a/odl-aaa-moon/aaa-idp-mapping/pom.xml b/odl-aaa-moon/aaa-idp-mapping/pom.xml
new file mode 100644
index 00000000..99c2322d
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-idpmapping</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>bundle</packaging>
+
+ <properties>
+ <powermock.version>1.5.2</powermock.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.aaa.idpmapping.Activator</Bundle-Activator>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..7342485e
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Activator.java
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 00000000..00328b60
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/IdpJson.java
@@ -0,0 +1,248 @@
+/*
+ * 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 &lt;jdennis@redhat.com&gt;
+ */
+
+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<Object> loadJsonArray(JsonParser parser, Event event) {
+ List<Object> list = new ArrayList<Object>();
+
+ 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<String, Object> loadJsonObject(JsonParser parser, Event event) {
+ Map<String, Object> map = new LinkedHashMap<String, Object>();
+
+ 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<String, Object> properties = new HashMap<String, Object>(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<Object> list = (List<Object>) obj;
+ dumpJsonArray(generator, list);
+ } else if (obj instanceof Map) {
+ generator.writeStartObject();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) 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<Object> list) {
+ for (Object obj : list) {
+ dumpJsonItem(generator, obj);
+ }
+ generator.writeEnd();
+ }
+
+ private void dumpJsonObject(JsonGenerator generator, Map<String, Object> map) {
+
+ for (Map.Entry<String, Object> 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<Object> list = (List<Object>) obj;
+ dumpJsonArray(generator, list);
+ } else if (obj instanceof Map) {
+ generator.writeStartObject(key);
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map1 = (Map<String, Object>) 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
new file mode 100644
index 00000000..1e42f4f2
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidRuleException.java
@@ -0,0 +1,35 @@
+/*
+ * 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 &lt;jdennis@redhat.com&gt;
+ */
+
+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
new file mode 100644
index 00000000..fb8b132f
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidTypeException.java
@@ -0,0 +1,35 @@
+/*
+ * 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 &lt;jdennis@redhat.com&gt;
+ */
+
+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
new file mode 100644
index 00000000..2f83c13f
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/InvalidValueException.java
@@ -0,0 +1,35 @@
+/*
+ * 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 &lt;jdennis@redhat.com&gt;
+ */
+
+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
new file mode 100644
index 00000000..0f86fde6
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java
@@ -0,0 +1,1368 @@
+/*
+ * 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 &lt;jdennis@redhat.com&gt;
+ */
+
+public class RuleProcessor {
+ private static final Logger LOG = LoggerFactory.getLogger(RuleProcessor.class);
+
+ public String ruleIdFormat = "<rule [${rule_number}:\"${rule_name}\"]>";
+ public String statementIdFormat = "<rule [${rule_number}:\"${rule_name}\"] block [${block_number}:\"${block_name}\"] statement ${statement_number}>";
+
+ /*
+ * 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<Map<String, Object>> rules = null;
+ boolean success = true;
+ Map<String, Map<String, Object>> mappings = null;
+
+ public RuleProcessor(java.io.Reader rulesIn, Map<String, Map<String, Object>> mappings) {
+ this.mappings = mappings;
+ IdpJson json = new IdpJson();
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> loadJson = (List<Map<String, Object>>) json.loadJson(rulesIn);
+ rules = loadJson;
+ }
+
+ public RuleProcessor(Path rulesIn, Map<String, Map<String, Object>> mappings)
+ throws IOException {
+ this.mappings = mappings;
+ IdpJson json = new IdpJson();
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> loadJson = (List<Map<String, Object>>) json.loadJson(rulesIn);
+ rules = loadJson;
+ }
+
+ public RuleProcessor(String rulesIn, Map<String, Map<String, Object>> mappings) {
+ this.mappings = mappings;
+ IdpJson json = new IdpJson();
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> loadJson = (List<Map<String, Object>>) 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<String, String> regexpGroupMap(String pattern, Matcher matcher) {
+ Map<String, String> groupMap = new HashMap<String, String>();
+ 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<Object> 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<String> regexpGroupList(Matcher matcher) {
+ List<String> groupList = new ArrayList<String>(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<Object> list = (List<Object>) 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<String, Object> map = (Map<String, Object>) obj;
+ boolean first = true;
+
+ sw.write('{');
+ for (Map.Entry<String, Object> 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<Object> new_list = new ArrayList<Object>();
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>) obj;
+ for (Object item : list) {
+ new_list.add(deepCopy(item));
+ }
+ return new_list;
+ } else if (obj instanceof Map) {
+ Map<String, Object> new_map = new LinkedHashMap<String, Object>();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) obj;
+ for (Map.Entry<String, Object> 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<String, Object> namespace) {
+ return substituteVariables(ruleIdFormat, namespace);
+ }
+
+ public String statementId(Map<String, Object> namespace) {
+ return substituteVariables(statementIdFormat, namespace);
+ }
+
+ public String substituteVariables(String string, Map<String, Object> 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<String, Object> getMapping(Map<String, Object> namespace, Map<String, Object> rule) {
+ Map<String, Object> mapping = null;
+ String mappingName = null;
+
+ try {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) 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<Object> 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<Object> statement, int index,
+ Map<String, Object> namespace, Set<TokenStorageType> storageTypes,
+ Set<TokenType> 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<Object> statement, int index,
+ Map<String, Object> namespace, Set<TokenType> 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<Object> statement, int index,
+ Set<TokenType> 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<Object> statement, int index,
+ Map<String, Object> 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<String, Object> process(String assertionJson) {
+ ProcessResult result;
+ IdpJson json = new IdpJson();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> assertion = (Map<String, Object>) json.loadJson(assertionJson);
+ LOG.info("Assertion JSON: {}", json.dumpJson(assertion));
+ this.success = true;
+
+ for (int ruleNumber = 0; ruleNumber < this.rules.size(); ruleNumber++) {
+ Map<String, Object> namespace = new HashMap<String, Object>();
+ Map<String, Object> rule = (Map<String, Object>) 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<String, Object> mapped = new LinkedHashMap<String, Object>();
+ Map<String, Object> mapping = getMapping(namespace, rule);
+ for (Map.Entry<String, Object> entry : ((Map<String, Object>) 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<String, Object> namespace, Map<String, Object> rule) {
+ ProcessResult result = ProcessResult.BLOCK_CONTINUE;
+ @SuppressWarnings("unchecked")
+ List<List<List<Object>>> statementBlocks = (List<List<List<Object>>>) rule.get("statement_blocks");
+ if (statementBlocks == null) {
+ throw new InvalidRuleException("rule missing 'statement_blocks'");
+
+ }
+ for (int blockNumber = 0; blockNumber < statementBlocks.size(); blockNumber++) {
+ List<List<Object>> block = (List<List<Object>>) 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<String, Object> namespace, List<List<Object>> block) {
+ ProcessResult result = ProcessResult.STATEMENT_CONTINUE;
+
+ for (int statementNumber = 0; statementNumber < block.size(); statementNumber++) {
+ List<Object> statement = (List<Object>) 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<String, Object> namespace, List<Object> 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<String, Object> namespace, List<Object> 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<String, Object> namespace,
+ List<Object> 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<String, Object> namespace,
+ List<Object> 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<String, Object> namespace,
+ List<Object> 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<Object> 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<String, Object> namespace,
+ List<Object> statement) {
+ Token variable = getVariable(verb, statement, 1, namespace);
+ Token array = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY));
+
+ List<Object> newValue = new ArrayList<Object>();
+ Set<Object> seen = new HashSet<Object>();
+
+ 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<String, Object> namespace,
+ List<Object> 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<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));
+ }
+ try {
+ newValue = new ArrayList<String>(
+ 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<String, Object> namespace,
+ List<Object> 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<String, Object> namespace,
+ List<Object> 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<Object> oldValue = parameter.getListValue();
+ List<Object> newValue = new ArrayList<Object>(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<String, Object> oldValue = parameter.getMapValue();
+ Map<String, Object> newValue = new LinkedHashMap<String, Object>(oldValue.size());
+
+ for (Map.Entry<String, Object> 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<String, Object> namespace,
+ List<Object> 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<Object> oldValue = parameter.getListValue();
+ List<Object> newValue = new ArrayList<Object>(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<String, Object> oldValue = parameter.getMapValue();
+ Map<String, Object> newValue = new LinkedHashMap<String, Object>(oldValue.size());
+
+ for (Map.Entry<String, Object> 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<String, Object> namespace, List<Object> 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<String, Object> namespace,
+ List<Object> 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<String, Object> namespace,
+ List<Object> 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<Object> leftValue = left.getListValue();
+ List<Object> rightValue = right.getListValue();
+ result = leftValue.equals(rightValue);
+ }
+ break;
+ case MAP: {
+ Map<String, Object> leftValue = left.getMapValue();
+ Map<String, Object> 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<String, Object> namespace,
+ List<Object> 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<Object>());
+ namespace.put(REGEXP_MAP_VARIABLE, new HashMap<String, Object>());
+ }
+
+ 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<String, Object> namespace,
+ List<Object> 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<String, Object> namespace,
+ List<Object> 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<String, Object> namespace,
+ List<Object> 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
new file mode 100644
index 00000000..6abab3ee
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/StatementErrorException.java
@@ -0,0 +1,35 @@
+/*
+ * 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 &lt;jdennis@redhat.com&gt;
+ */
+
+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
new file mode 100644
index 00000000..402fb064
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/Token.java
@@ -0,0 +1,401 @@
+/*
+ * 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 <jdennis@redhat.com>
+ */
+
+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 = "(?<!\\\\)\\$" + // non-escaped $
+ // sign
+ "\\{?" + // optional delimiting brace
+ "([a-zA-Z][a-zA-Z0-9_]*)" + // group 1: variable name
+ "(\\[" + // group 2: optional index
+ "([a-zA-Z0-9_]+)" + // group 3: array index
+ "\\])?" + // end optional index
+ "\\}?"; // optional delimiting brace
+ public static final Pattern VARIABLE_RE = Pattern.compile(VARIABLE_PAT);
+ /*
+ * Requires only a variable to be present in the string but permits leading
+ * and trailing whitespace.
+ */
+ private static final String VARIABLE_ONLY_PAT = "^\\s*" + VARIABLE_PAT + "\\s*$";
+ public static final Pattern VARIABLE_ONLY_RE = Pattern.compile(VARIABLE_ONLY_PAT);
+
+ private Object value = null;
+
+ public Map<String, Object> namespace = null;
+ public TokenStorageType storageType = TokenStorageType.UNKNOWN;
+ public TokenType type = TokenType.UNKNOWN;
+ public String name = null;
+ public String index = null;
+
+ Token(Object input, Map<String, Object> 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<Object> list = (List<Object>) 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<String, Object> map = (Map<String, Object>) 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<Object> list = (List<Object>) 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<String, Object> map = (Map<String, Object>) 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<Object> getListValue() {
+ if (this.type == TokenType.ARRAY) {
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>) this.value;
+ return list;
+ } else {
+ throw new InvalidTypeException(String.format("expected %s value but token type is %s",
+ TokenType.ARRAY, this.type));
+ }
+ }
+
+ public Map<String, Object> getMapValue() {
+ if (this.type == TokenType.MAP) {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) 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
new file mode 100644
index 00000000..7200da3d
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/UndefinedValueException.java
@@ -0,0 +1,34 @@
+/*
+ * 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 &lt;jdennis@redhat.com&gt;
+ */
+
+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
new file mode 100644
index 00000000..84d403f9
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/RuleProcessorTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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<Object> list = new ArrayList<Object>();
+ list.add("str1");
+ list.add("str2");
+ list.add("str3");
+ assertEquals("str1/str2/str3", RuleProcessor.join(list, "/"));
+ }
+
+ @Test
+ public void testSubstituteVariables() {
+ Map<String, Object> namespace = new HashMap<String, Object>() {
+ {
+ put("foo1", new HashMap<String, String>() {
+ {
+ put("0", "1");
+ }
+ });
+ }
+ };
+ String str = "foo1[0]";
+ String subVariable = ruleProcess.substituteVariables(str, namespace);
+ assertNotNull(subVariable);
+ assertEquals(subVariable, str);
+ }
+
+ @Test
+ public void testGetMapping() {
+ Map<String, Object> namespace = new HashMap<String, Object>() {
+ {
+ put("foo1", new HashMap<String, String>() {
+ {
+ put("0", "1");
+ }
+ });
+ }
+ };
+ final Map<String, Object> item = new HashMap<String, Object>() {
+ {
+ put("str", "val");
+ }
+ };
+ Map<String, Object> rules = new HashMap<String, Object>() {
+ {
+ put("mapping", item);
+ put("mapping_name", "mapping");
+ }
+ };
+ Map<String, Object> 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<String, Object> mapping = new HashMap<String, Object>() {
+ {
+ put("Name", "Admin");
+ }
+ };
+ List<Map<String, Object>> internalRules = new ArrayList<Map<String, Object>>();
+ Map<String, Object> internalRule = new HashMap<String, Object>() {
+ {
+ 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
new file mode 100644
index 00000000..d6181051
--- /dev/null
+++ b/odl-aaa-moon/aaa-idp-mapping/src/test/java/org/opendaylight/aaa/idpmapping/TokenTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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<String, Object> namespace = new HashMap<String, Object>() {
+ {
+ put("foo1", new HashMap<String, String>() {
+ {
+ 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<String, Object>()), 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
new file mode 100644
index 00000000..d8507c6d
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro-act/pom.xml
@@ -0,0 +1,84 @@
+<!-- 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 -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-shiro-act</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-shiro</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ <version>1.8.3</version>
+ </dependency>
+
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${bundle.plugin.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.aaa.shiroact.Activator</Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..0012a0bd
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro-act/src/main/java/org/opendaylight/aaa/shiroact/Activator.java
@@ -0,0 +1,51 @@
+/*
+ * 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
+ * <code>odl-aaa-shiro</code> feature in your feature definition.
+ *
+ * Offers contextual <code>DEBUG</code> level clues concerning the activation of
+ * the <code>aaa-shiro-act</code> bundle. To enable the enhanced debugging issue
+ * the following line in the karaf shell:
+ * <code>log:set debug org.opendaylight.aaa.shiroact.Activator</code>
+ *
+ * @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
new file mode 100644
index 00000000..23eef9db
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro-act/src/test/java/org/opendaylight/aaa/shiroact/ActivatorTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 00000000..2f848215
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/pom.xml
@@ -0,0 +1,156 @@
+<!-- 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 -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-shiro</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <!-- moon add -->
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-client</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>20140107</version>
+ </dependency>
+ <!-- OAuth2 dependencies for moon -->
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.common</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-sts</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-basic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ <version>1.8.3</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${bundle.plugin.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ *
+ </Import-Package>
+ <Web-ContextPath>/moon</Web-ContextPath>
+ <Bundle-Activator>org.opendaylight.aaa.shiro.Activator</Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/shiro.ini</file>
+ <type>cfg</type>
+ <classifier>configuration</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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
new file mode 100644
index 00000000..2f1c98f7
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java
@@ -0,0 +1,45 @@
+/*
+ * 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
+ * <code>odl-restconf-noauth</code> feature.
+ *
+ * This class is also responsible for offering contextual <code>DEBUG</code>
+ * level clues concerning the activation of the <code>aaa-shiro</code> bundle.
+ * To enable these debug messages, issue the following command in the karaf
+ * shell: <code>log:set debug org.opendaylight.aaa.shiro.Activator</code>
+ *
+ * @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
new file mode 100644
index 00000000..e4485d73
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java
@@ -0,0 +1,94 @@
+/*
+ * 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
+ * <code>ServiceProxy.getInstance().setEnabled(true)</code>. AuthN and AuthZ are
+ * disabled by default in order to support workflows such as the feature
+ * <code>odl-restconf-noauth</code>.
+ *
+ * The AAA service is enabled through installing the <code>odl-aaa-shiro</code>
+ * feature. The <code>org.opendaylight.aaa.shiroact.Activator()</code>
+ * 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 <code>getInstance()</code> function.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ * @see <a
+ * href="https://github.com/opendaylight/netconf/blob/master/opendaylight/restconf/sal-rest-connector/src/main/resources/WEB-INF/web.xml">resconf
+ * web,xml</a>
+ * @see <code>org.opendaylight.aaa.shiro.Activator</code>
+ * @see <code>org.opendaylight.aaa.shiro.filters.AAAFilter</code>
+ */
+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 <code>odl-restconf-noauth</code>
+ */
+ 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
new file mode 100644
index 00000000..e768ea59
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java
@@ -0,0 +1,38 @@
+/*
+ * 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 <code>Logger.output("message")</code>.
+ *
+ * @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
+ * <code>Accounter.getInstance().output("message")</code>.
+ */
+ private Accounter() {
+ }
+
+ /**
+ * Account for a particular <code>message</code>
+ *
+ * @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
new file mode 100644
index 00000000..9e84c988
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java
@@ -0,0 +1,78 @@
+/*
+ * 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:
+ * <code>$KARAF_HOME/shiro.ini</code>
+ *
+ * An important distinction to consider is that Shiro URL rules work to protect
+ * the system at the Web layer, and <code>AuthzDomDataBroker</code> 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<RBACRule> rbacRules = new HashSet<RBACRule>();
+
+ /**
+ * 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 <code>DefaultRBACRules</code>.
+ */
+ public final Collection<RBACRule> 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 <code>DefaultRBACRules</code>.
+ //
+ // 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
new file mode 100644
index 00000000..0da95eb4
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java
@@ -0,0 +1,170 @@
+/*
+ * 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:
+ * <code>urlPattern=roles[atLeastOneCommaSeperatedRole]</code>
+ *
+ * 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:
+ * <code>RBACRule.createRBACRule()</code>
+ *
+ * @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<String> roles = new HashSet<String>();
+
+ /**
+ * 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 <code>urlPattern</code> or <code>roles</code> is null
+ * @throws IllegalArgumentException
+ * if <code>urlPattern</code> is an empty string or
+ * <code>roles</code> is an empty collection.
+ */
+ private RBACRule(final String urlPattern, final Collection<String> 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<String> 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 <code>RBACRule</code>.
+ */
+ public Collection<String> 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<String> 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<String> roles) throws NullPointerException,
+ IllegalArgumentException {
+
+ Preconditions.checkNotNull(roles);
+ checkRolesCollectionSize(roles);
+ this.roles = roles;
+ }
+
+ /**
+ * Generates a string representation of the <code>RBACRule</code> roles in
+ * shiro form.
+ *
+ * @return roles string representation in the form
+ * <code>roles[roleOne,roleTwo]</code>
+ */
+ public String getRolesInShiroFormat() {
+ final String ROLES_STRING = "roles";
+ return ROLES_STRING + Arrays.toString(roles.toArray());
+ }
+
+ /**
+ * Generates the string representation of the <code>RBACRule</code> in shiro
+ * form. For example: <code>urlPattern=roles[admin,user]</code>
+ */
+ @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
new file mode 100644
index 00000000..b53588d8
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java
@@ -0,0 +1,72 @@
+/*
+ * 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:
+ *
+ * <code>log:set debug org.opendaylight.aaa.shiro.filters.AAAFilter</code>
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ * @see <code>javax.servlet.Filter</code>
+ * @see <code>org.apache.shiro.web.servlet.ShiroFilter</code>
+ */
+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
+ * <code>ServiceProxy.getInstance.getEnabled()</code>.
+ *
+ * @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
new file mode 100644
index 00000000..06038c54
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java
@@ -0,0 +1,187 @@
+/*
+ * 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
new file mode 100644
index 00000000..90b0101e
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java
@@ -0,0 +1,78 @@
+/*
+ * 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 <code>BasicHttpAuthenticationFilter</code> to include ability to
+ * authenticate OAuth2 tokens, which is needed for backwards compatibility with
+ * <code>TokenAuthFilter</code>.
+ *
+ * This behavior is enabled by default for backwards compatibility. To disable
+ * OAuth2 functionality, just comment out the following line from the
+ * <code>etc/shiro.ini</code> file:
+ * <code>authcBasic = org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter</code>
+ * 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
new file mode 100644
index 00000000..a95b4e7f
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java
@@ -0,0 +1,155 @@
+/*
+ * 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<String> roles;
+ private final String token;
+
+
+ public MoonPrincipal(String username, String domain, String userId, Set<String> 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<String> 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<String> 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<String> roles;
+
+ public MoonClaim(String clientId, String userId, String user, String domain, Set<String> roles) {
+ this.clientId = clientId;
+ this.userId = userId;
+ this.user = user;
+ this.domain = domain;
+ this.roles = ImmutableSet.<String> 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<String> 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<String> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(ImmutableSet<String> 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
new file mode 100644
index 00000000..a954a606
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java
@@ -0,0 +1,30 @@
+/*
+ * 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
new file mode 100644
index 00000000..63288c23
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <servlet>
+ <servlet-name>MOON</servlet-name>
+ <servlet-class>org.opendaylight.aaa.shiro.moon.MoonTokenEndpoint</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>MOON</servlet-name>
+ <url-pattern>/token</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>MOON</servlet-name>
+ <url-pattern>/revoke</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>MOON</servlet-name>
+ <url-pattern>/validate</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>MOON</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <!-- Shiro Filter -->
+ <context-param>
+ <param-name>shiroEnvironmentClass</param-name>
+ <param-value>org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment</param-value>
+ </context-param>
+
+ <listener>
+ <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
+ </listener>
+
+ <filter>
+ <filter-name>ShiroFilter</filter-name>
+ <filter-class>org.opendaylight.aaa.shiro.filters.AAAFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>ShiroFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+</web-app> \ 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
new file mode 100644
index 00000000..d84f9fa0
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/resources/shiro.ini
@@ -0,0 +1,95 @@
+#
+# 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://<URL>: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://<URL>: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
new file mode 100644
index 00000000..2d9c8976
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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
new file mode 100644
index 00000000..38658f0c
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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<RBACRule> 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
new file mode 100644
index 00000000..825fe626
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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<String> 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<String> 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<String>()));
+ }
+
+ @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
new file mode 100644
index 00000000..22ce203f
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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<SearchResult> {
+
+ /**
+ * state variable
+ */
+ boolean first = true;
+
+ /**
+ * returned the first time <code>next()</code> or
+ * <code>nextElement()</code> 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 <code>searchResult</code> 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 <code>searchResult</code> 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<String> collection = new Vector<String>();
+
+ public TestPrincipalCollection(String element) {
+ collection.add(element);
+ }
+
+ @Override
+ public Iterator<String> iterator() {
+ return collection.iterator();
+ }
+
+ @Override
+ public List<String> asList() {
+ return collection;
+ }
+
+ @Override
+ public Set<String> asSet() {
+ HashSet<String> set = new HashSet<String>();
+ set.addAll(collection);
+ return set;
+ }
+
+ @Override
+ public <T> Collection<T> byType(Class<T> arg0) {
+ return null;
+ }
+
+ @Override
+ public Collection<String> fromRealm(String arg0) {
+ return collection;
+ }
+
+ @Override
+ public Object getPrimaryPrincipal() {
+ return collection.firstElement();
+ }
+
+ @Override
+ public Set<String> getRealmNames() {
+ return null;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return collection.isEmpty();
+ }
+
+ @Override
+ public <T> T oneByType(Class<T> 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<String> roleNames = new HashSet<String>();
+ 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<String> 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
new file mode 100644
index 00000000..f2eb92b5
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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<String, List<String>> expectedHeaders = new HashMap<String, List<String>>();
+ expectedHeaders.put("Authorization", Lists.newArrayList(authHeader));
+ final Map<String, List<String>> actualHeaders = formHeadersWithToken(authHeader);
+ List<String> 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<String, List<String>> expectedHeaders = new HashMap<String, List<String>>();
+ expectedHeaders.put("Authorization", Lists.newArrayList(authHeader));
+ final Map<String, List<String>> actualHeaders = formHeaders(username, password, domain);
+ List<String> 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
new file mode 100644
index 00000000..141d0ce5
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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
new file mode 100644
index 00000000..8efad137
--- /dev/null
+++ b/odl-aaa-moon/artifacts/pom.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2013 Robert Varga. 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>odlparent-lite</artifactId>
+ <version>1.6.1-Beryllium-SR1</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-artifacts</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>pom</packaging>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn</artifactId>
+ <version>${project.version}</version>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-basic</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-federation</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-federation</artifactId>
+ <version>${project.version}</version>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-keystone</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-mdsal-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-mdsal-store-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-mdsal-config</artifactId>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-shiro</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-shiro-act</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-sssd</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-store</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-store</artifactId>
+ <version>${project.version}</version>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-sts</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authz-model</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authz-service</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>authz-service-config</artifactId>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>authz-restconf-config</artifactId>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-credential-store-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-idmlight</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-idmlight</artifactId>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-idpmapping</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>features-aaa-api</artifactId>
+ <version>${project.version}</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>features-aaa-authn</artifactId>
+ <version>${project.version}</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>features-aaa-authz</artifactId>
+ <version>${project.version}</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-h2-store</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-h2-store</artifactId>
+ <version>${project.version}</version>
+ <classifier>config</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>features-aaa-shiro</artifactId>
+ <version>${project.version}</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>features-aaa</artifactId>
+ <version>${project.version}</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <properties>
+ <nexusproxy>http://nexus.opendaylight.org/content</nexusproxy>
+ </properties>
+
+ <distributionManagement>
+ <!-- OpenDayLight Released artifact -->
+ <repository>
+ <id>opendaylight-release</id>
+ <url>${nexusproxy}/repositories/opendaylight.release/</url>
+ </repository>
+ <!-- OpenDayLight Snapshot artifact -->
+ <snapshotRepository>
+ <id>opendaylight-snapshot</id>
+ <url>${nexusproxy}/repositories/opendaylight.snapshot/</url>
+ </snapshotRepository>
+ </distributionManagement>
+</project>
diff --git a/odl-aaa-moon/commons/docs/AuthNusecases.vsd b/odl-aaa-moon/commons/docs/AuthNusecases.vsd
new file mode 100644
index 00000000..ddd59fb3
--- /dev/null
+++ b/odl-aaa-moon/commons/docs/AuthNusecases.vsd
Binary files differ
diff --git a/odl-aaa-moon/commons/docs/direct_authn.png b/odl-aaa-moon/commons/docs/direct_authn.png
new file mode 100644
index 00000000..f63f038e
--- /dev/null
+++ b/odl-aaa-moon/commons/docs/direct_authn.png
Binary files differ
diff --git a/odl-aaa-moon/commons/docs/federated_authn1.png b/odl-aaa-moon/commons/docs/federated_authn1.png
new file mode 100644
index 00000000..199f6f4d
--- /dev/null
+++ b/odl-aaa-moon/commons/docs/federated_authn1.png
Binary files differ
diff --git a/odl-aaa-moon/commons/docs/federated_authn2.png b/odl-aaa-moon/commons/docs/federated_authn2.png
new file mode 100644
index 00000000..b71e9aa7
--- /dev/null
+++ b/odl-aaa-moon/commons/docs/federated_authn2.png
Binary files differ
diff --git a/odl-aaa-moon/commons/federation/README b/odl-aaa-moon/commons/federation/README
new file mode 100644
index 00000000..dd9cdbf0
--- /dev/null
+++ b/odl-aaa-moon/commons/federation/README
@@ -0,0 +1,271 @@
+README
+===============================================================================
+Federated AAA is deployed using several config files. This file explains a
+simple scenario utilizing two servers:
+a) ipa.example.com
+ - Runs the IPA Server Software
+b) odl.example.com
+ - Runs the IPA Client Software
+ - Runs an Apache proxy frontend (AuthN through mod_lookup_identity.so)
+ - Runs ODL
+
+This setup for this scenario is illustrated in Figure 1 below:
+
+ -----------------------
+ | odl.example.com |
+ | (Fedora 20 Linux) |
+ | |
+ | ------------------- |
+ | | ODL Jetty Server | |
+ | | (Port 8181 & 8383)| |
+ | ------------------- |
+ | ^ . |
+ | . (Apache . | SSSD Requests/Responses
+ | . Reverse . | /
+ | . Proxy) . | /
+ | . v | /
+ | ------------------- | | ------------------
+ | | Apache |<|..................| ipa.example.com |
+ | | (Port 80) |.|.................>| (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
+<Location "/*">
+ AuthType Kerberos
+ AuthName "Kerberos Login"
+ KrbMethodNegotiate On
+ KrbMethodK5Passwd on
+ KrbAuthRealms EXAMPLE.COM
+ Krb5KeyTab /etc/krb5.keytab
+ require valid-user
+</Location>
+
+
+<LocationMatch "/*">
+
+ 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
+</LocationMatch>
+
+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
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//
+DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
+
+<Configure class="org.eclipse.jetty.server.Server">
+
+ <!-- =========================================================== -->
+ <!-- Set connectors -->
+ <!-- =========================================================== -->
+ <!-- One of each type! -->
+ <!-- =========================================================== -->
+
+ <!-- Use this connector for many frequently idle connections and for
+ threadless continuations. -->
+ <Call name="addConnector">
+ <Arg>
+ <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+ <Set name="host">
+ <Property name="jetty.host" />
+ </Set>
+ <Set name="port">
+ <Property name="jetty.port" default="8181" />
+ </Set>
+ <Set name="maxIdleTime">300000</Set>
+ <Set name="Acceptors">2</Set>
+ <Set name="statsOn">false</Set>
+ <Set name="confidentialPort">8443</Set>
+ <Set name="lowResourcesConnections">20000</Set>
+ <Set name="lowResourcesMaxIdleTime">5000</Set>
+ </New>
+ </Arg>
+ </Call>
+ <!-- Trusted Authentication Federation proxy connection -->
+ <Call name="addConnector">
+ <Arg>
+ <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+ <Set name="host">127.0.0.1</Set>
+ <Set name="port">8383</Set>
+ <Set name="maxIdleTime">300000</Set>
+ <Set name="Acceptors">2</Set>
+ <Set name="statsOn">false</Set>
+ <Set name="confidentialPort">8445</Set>
+ <Set name="name">federationConn</Set>
+ <Set name="lowResourcesConnections">20000</Set>
+ <Set name="lowResourcesMaxIdleTime">5000</Set>
+ </New>
+ </Arg>
+ </Call>
+ <!-- =========================================================== -->
+ <!-- Configure Authentication Realms -->
+ <!-- Realms may be configured for the entire server here, or -->
+ <!-- they can be configured for a specific web app in a context -->
+ <!-- configuration (see $(jetty.home)/contexts/test.xml for an -->
+ <!-- example). -->
+ <!-- =========================================================== -->
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
+ <Set name="name">karaf</Set>
+ <Set name="loginModuleName">karaf</Set>
+ <Set name="roleClassNames">
+ <Array type="java.lang.String">
+ <Item>org.apache.karaf.jaas.boot.principal.RolePrincipal
+ </Item>
+ </Array>
+ </Set>
+ </New>
+ </Arg>
+ </Call>
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
+ <Set name="name">default</Set>
+ <Set name="loginModuleName">karaf</Set>
+ <Set name="roleClassNames">
+ <Array type="java.lang.String">
+ <Item>org.apache.karaf.jaas.boot.principal.RolePrincipal
+ </Item>
+ </Array>
+ </Set>
+ </New>
+ </Arg>
+ </Call>
+</Configure>
+
+
+
+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=<PUT RESULT FROM ABOVE STEP HERE>&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 <PUT RESULT FROM ABOVE STEP HERE>' 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
new file mode 100644
index 00000000..98bacb0a
--- /dev/null
+++ b/odl-aaa-moon/commons/federation/idp_mapping_rules.json.example
@@ -0,0 +1,30 @@
+[
+ {
+ "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
new file mode 100644
index 00000000..c4cb2a7d
--- /dev/null
+++ b/odl-aaa-moon/commons/federation/jetty.xml.example
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//
+DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
+
+<Configure class="org.eclipse.jetty.server.Server">
+
+ <!-- =========================================================== -->
+ <!-- Set connectors -->
+ <!-- =========================================================== -->
+ <!-- One of each type! -->
+ <!-- =========================================================== -->
+
+ <!-- Use this connector for many frequently idle connections and for
+ threadless continuations. -->
+ <Call name="addConnector">
+ <Arg>
+ <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+ <Set name="host">
+ <Property name="jetty.host" />
+ </Set>
+ <Set name="port">
+ <Property name="jetty.port" default="8181" />
+ </Set>
+ <Set name="maxIdleTime">300000</Set>
+ <Set name="Acceptors">2</Set>
+ <Set name="statsOn">false</Set>
+ <Set name="confidentialPort">8443</Set>
+ <Set name="lowResourcesConnections">20000</Set>
+ <Set name="lowResourcesMaxIdleTime">5000</Set>
+ </New>
+ </Arg>
+ </Call>
+ <!-- Trusted Authentication Federation proxy connection -->
+ <Call name="addConnector">
+ <Arg>
+ <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+ <Set name="host">127.0.0.1</Set>
+ <Set name="port">8383</Set>
+ <Set name="maxIdleTime">300000</Set>
+ <Set name="Acceptors">2</Set>
+ <Set name="statsOn">false</Set>
+ <Set name="confidentialPort">8445</Set>
+ <Set name="name">federationConn</Set>
+ <Set name="lowResourcesConnections">20000</Set>
+ <Set name="lowResourcesMaxIdleTime">5000</Set>
+ </New>
+ </Arg>
+ </Call>
+ <!-- =========================================================== -->
+ <!-- Configure Authentication Realms -->
+ <!-- Realms may be configured for the entire server here, or -->
+ <!-- they can be configured for a specific web app in a context -->
+ <!-- configuration (see $(jetty.home)/contexts/test.xml for an -->
+ <!-- example). -->
+ <!-- =========================================================== -->
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
+ <Set name="name">karaf</Set>
+ <Set name="loginModuleName">karaf</Set>
+ <Set name="roleClassNames">
+ <Array type="java.lang.String">
+ <Item>org.apache.karaf.jaas.boot.principal.RolePrincipal
+ </Item>
+ </Array>
+ </Set>
+ </New>
+ </Arg>
+ </Call>
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
+ <Set name="name">default</Set>
+ <Set name="loginModuleName">karaf</Set>
+ <Set name="roleClassNames">
+ <Array type="java.lang.String">
+ <Item>org.apache.karaf.jaas.boot.principal.RolePrincipal
+ </Item>
+ </Array>
+ </Set>
+ </New>
+ </Arg>
+ </Call>
+</Configure>
+
diff --git a/odl-aaa-moon/commons/federation/my_app.conf.example b/odl-aaa-moon/commons/federation/my_app.conf.example
new file mode 100644
index 00000000..71c8ad87
--- /dev/null
+++ b/odl-aaa-moon/commons/federation/my_app.conf.example
@@ -0,0 +1,31 @@
+LoadModule lookup_identity_module modules/mod_lookup_identity.so
+
+<Location "/*">
+ AuthType Kerberos
+ AuthName "Kerberos Login"
+ KrbMethodNegotiate On
+ KrbMethodK5Passwd on
+ KrbAuthRealms EXAMPLE.COM
+ Krb5KeyTab /etc/krb5.keytab
+ require valid-user
+</Location>
+
+
+<LocationMatch "/*">
+
+ 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
+</LocationMatch>
+
+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
new file mode 100644
index 00000000..15193a70
--- /dev/null
+++ b/odl-aaa-moon/commons/postman_examples/AAA_AuthZ_MDSAL.json.postman_collection
@@ -0,0 +1,77 @@
+{
+ "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
new file mode 100644
index 00000000..dc65d84f
--- /dev/null
+++ b/odl-aaa-moon/distribution-karaf/pom.xml
@@ -0,0 +1,274 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>distribution-karaf</artifactId>
+ <packaging>pom</packaging>
+ <prerequisites>
+ <maven>3.0</maven>
+ </prerequisites>
+
+ <dependencies>
+ <!-- Basic Karaf dependencies -->
+ <dependency>
+ <groupId>org.apache.karaf.features</groupId>
+ <artifactId>framework</artifactId>
+ <version>${karaf.version}</version>
+ <type>kar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.features</groupId>
+ <artifactId>standard</artifactId>
+ <version>${karaf.version}</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ <scope>runtime</scope>
+ </dependency>
+
+ <!-- ODL Branding -->
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>karaf.branding</artifactId>
+ <version>${karaf.branding.version}</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <!-- ODL Resources needed for karaf -->
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>opendaylight-karaf-resources</artifactId>
+ <version>${karaf.resources.version}</version>
+ </dependency>
+
+ <!-- Project local feautures -->
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa-api</artifactId>
+ <classifier>features</classifier>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa</artifactId>
+ <classifier>features</classifier>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa-authz</artifactId>
+ <classifier>features</classifier>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa-shiro</artifactId>
+ <classifier>features</classifier>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <scope>runtime</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <versionRange>[0,)</versionRange>
+ <goals>
+ <goal>cleanVersions</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <versionRange>[0,)</versionRange>
+ <goals>
+ <goal>copy</goal>
+ <goal>unpack</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.apache.karaf.tooling</groupId>
+ <artifactId>karaf-maven-plugin</artifactId>
+ <versionRange>[0,)</versionRange>
+ <goals>
+ <goal>commands-generate-help</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.fusesource.scalate</groupId>
+ <artifactId>maven-scalate-plugin</artifactId>
+ <versionRange>[0,)</versionRange>
+ <goals>
+ <goal>sitegen</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.apache.servicemix.tooling</groupId>
+ <artifactId>depends-maven-plugin</artifactId>
+ <versionRange>[0,)</versionRange>
+ <goals>
+ <goal>generate-depends-file</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.karaf.tooling</groupId>
+ <artifactId>karaf-maven-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <bootFeatures>
+ <feature>standard</feature>
+ <!-- Optional TODO: Add entries here for the features
+ you want in your local distro Note: odl-restconf is a separate feature from
+ odl-mdsal-broker. If you want restconf, you need to list it here explicitely.
+ Examples: <feature>odl-toaster</feature> <feature>odl-restconf</feature> -->
+ <!-- Final TODO: Remove TODO Comments ;) -->
+ </bootFeatures>
+ </configuration>
+ <executions>
+ <execution>
+ <id>process-resources</id>
+ <goals>
+ <goal>install-kars</goal>
+ </goals>
+ <phase>process-resources</phase>
+ </execution>
+ <execution>
+ <id>package</id>
+ <goals>
+ <goal>instance-create-archive</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.6</version>
+ <executions>
+ <execution>
+ <id>copy</id>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>karaf.branding</artifactId>
+ <version>${karaf.branding.version}</version>
+ <outputDirectory>target/assembly/lib</outputDirectory>
+ <destFileName>karaf.branding-${karaf.branding.version}.jar</destFileName>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ <execution>
+ <id>unpack-karaf-resources</id>
+ <goals>
+ <goal>unpack-dependencies</goal>
+ </goals>
+ <phase>prepare-package</phase>
+ <configuration>
+ <outputDirectory>${project.build.directory}/assembly</outputDirectory>
+ <groupId>org.opendaylight.controller</groupId>
+ <includeArtifactIds>opendaylight-karaf-resources</includeArtifactIds>
+ <excludes>META-INF\/**</excludes>
+ <excludeTransitive>true</excludeTransitive>
+ <ignorePermissions>false</ignorePermissions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <tasks>
+ <chmod perm="755">
+ <fileset
+ dir="${project.build.directory}/assembly/bin">
+ <include name="karaf" />
+ <include name="instance" />
+ <include name="start" />
+ <include name="stop" />
+ <include name="status" />
+ <include name="client" />
+ <include name="shell" />
+ </fileset>
+ </chmod>
+ </tasks>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</developerConnection>
+ <tag>HEAD</tag>
+ <url>https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary</url>
+ </scm>
+</project>
diff --git a/odl-aaa-moon/features/api/pom.xml b/odl-aaa-moon/features/api/pom.xml
new file mode 100644
index 00000000..b64242ae
--- /dev/null
+++ b/odl-aaa-moon/features/api/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>features-parent</artifactId>
+ <version>1.6.1-Beryllium-SR1</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa-api</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>jar</packaging>
+
+ <properties>
+ <yangtools.version>0.8.1-Beryllium-SR1</yangtools.version>
+ <mdsal.version>2.0.1-Beryllium-SR1</mdsal.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- This project -->
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-artifacts</artifactId>
+ <version>${project.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+
+ <!-- YANG tools -->
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yangtools-artifacts</artifactId>
+ <version>${yangtools.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-credential-store-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>features-yangtools</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>features-mdsal</artifactId>
+ <version>2.0.1-Beryllium-SR1</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ </dependencies>
+
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</developerConnection>
+ <tag>HEAD</tag>
+ <url>https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary</url>
+ </scm>
+</project>
diff --git a/odl-aaa-moon/features/api/src/main/features/features.xml b/odl-aaa-moon/features/api/src/main/features/features.xml
new file mode 100644
index 00000000..c526e174
--- /dev/null
+++ b/odl-aaa-moon/features/api/src/main/features/features.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!-- 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 -->
+<features name="odl-aaa-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
+ <repository>mvn:org.opendaylight.yangtools/features-yangtools/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.mdsal/features-mdsal/{{VERSION}}/xml/features</repository>
+ <feature name='odl-aaa-api' description='OpenDaylight :: AAA :: APIs'
+ version='${project.version}'>
+ <bundle>mvn:com.sun.jersey/jersey-server/{{VERSION}}</bundle>
+ <bundle>mvn:com.sun.jersey/jersey-core/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-api/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-credential-store-api/{{VERSION}}</bundle>
+ <feature version='${yangtools.version}'>odl-yangtools-common</feature>
+ <feature version='${mdsal.version}'>odl-mdsal-binding-base</feature>
+ </feature>
+</features>
diff --git a/odl-aaa-moon/features/authn/pom.xml b/odl-aaa-moon/features/authn/pom.xml
new file mode 100644
index 00000000..d7d0def8
--- /dev/null
+++ b/odl-aaa-moon/features/authn/pom.xml
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>features-parent</artifactId>
+ <version>1.6.1-Beryllium-SR1</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>jar</packaging>
+
+ <properties>
+ <config.version>0.4.1-Beryllium-SR1</config.version>
+ <mdsal.version>2.0.1-Beryllium-SR1</mdsal.version>
+ <controller.mdsal.version>1.3.1-Beryllium-SR1</controller.mdsal.version>
+ <yangtools.version>0.8.1-Beryllium-SR1</yangtools.version>
+ <shiro.version>0.3.1-Beryllium-SR1</shiro.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- This project -->
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>${project.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <!-- shiro dependencies -->
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-shiro</artifactId>
+ <version>${shiro.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-shiro-act</artifactId>
+ <version>${shiro.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-web</artifactId>
+ </dependency>
+ <!-- odl-aaa-authn -->
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ </dependency>
+ <!-- jersey client for moon APIs calls -->
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.metatype</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jta_1.1_spec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-json-org</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-base</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-json-provider</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-jaxb-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa-api</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-sts</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-store</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-basic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-idmlight</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-idmlight</artifactId>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-idmlight</artifactId>
+ <version>${project.version}</version>
+ <type>py</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-federation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-mdsal-config</artifactId>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-store</artifactId>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-federation</artifactId>
+ <type>cfg</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-h2-store</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-h2-store</artifactId>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.enterprise</artifactId>
+ <version>4.2.0</version>
+ </dependency>
+
+ <!-- AuthN MD-SAL Cache dependencies -->
+
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-mdsal-store-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-mdsal-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>features-yangtools</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>features-mdsal</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>features-config</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-impl</artifactId>
+ </dependency>
+
+ <!-- odl-aaa-sssd -->
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-sssd</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-idpmapping</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-keystone</artifactId>
+ </dependency>
+ </dependencies>
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</developerConnection>
+ <tag>HEAD</tag>
+ <url>https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary</url>
+ </scm>
+</project>
diff --git a/odl-aaa-moon/features/authn/src/main/features/features.xml b/odl-aaa-moon/features/authn/src/main/features/features.xml
new file mode 100644
index 00000000..2c48d2c1
--- /dev/null
+++ b/odl-aaa-moon/features/authn/src/main/features/features.xml
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!-- 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 -->
+<features name="odl-aaa-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
+ <repository>mvn:org.opendaylight.aaa/features-aaa-api/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.yangtools/features-yangtools/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.controller/features-config/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.mdsal/features-mdsal/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.controller/features-mdsal/{{VERSION}}/xml/features</repository>
+
+ <feature name='odl-aaa-authn-no-cluster' description='OpenDaylight :: AAA :: Authentication - NO CLUSTER'
+ version='${project.version}'>
+ <feature version='${project.version}'>odl-aaa-api</feature>
+
+ <!-- MD-SAL -->
+ <feature version='${yangtools.version}'>odl-yangtools-common</feature>
+ <feature version='${mdsal.version}'>odl-mdsal-binding-base</feature>
+ <feature version='${controller.mdsal.version}'>odl-mdsal-broker</feature>
+ <feature version='${config.version}'>odl-config-core</feature>
+
+ <!-- REST -->
+ <feature>war</feature>
+ <bundle>mvn:com.sun.jersey/jersey-servlet/{{VERSION}}</bundle>
+ <bundle>mvn:com.sun.jersey/jersey-core/{{VERSION}}</bundle>
+ <bundle>mvn:com.sun.jersey/jersey-server/{{VERSION}}</bundle>
+
+ <!-- OSGi -->
+ <bundle>mvn:org.apache.felix/org.apache.felix.dependencymanager/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.felix/org.apache.felix.metatype/{{VERSION}}</bundle>
+
+ <!-- EhCache -->
+ <bundle>mvn:net.sf.ehcache/ehcache/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/{{VERSION}}</bundle>
+
+ <!-- OAuth -->
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.authzserver/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.resourceserver/{{VERSION}}</bundle>
+ <bundle>mvn:commons-codec/commons-codec/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:org.json/json/{{VERSION}}</bundle>
+
+ <!-- commons-lang -->
+ <bundle>wrap:mvn:org.apache.commons/commons-lang3/{{VERSION}}</bundle>
+
+ <!-- AuthN -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-shiro-act/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.shiro/shiro-core/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.shiro/shiro-web/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-sts/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-store/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-basic/{{VERSION}}</bundle>
+ <bundle>mvn:com.google.guava/guava/{{VERSION}}</bundle>
+
+ <!--H2 Store -->
+ <bundle>mvn:org.osgi/org.osgi.enterprise/4.2.0</bundle>
+ <bundle>wrap:mvn:com.h2database/h2/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-h2-store/{{VERSION}}</bundle>
+ <configfile finalname="etc/opendaylight/karaf/08-aaa-h2-store-config.xml">mvn:org.opendaylight.aaa/aaa-h2-store/{{VERSION}}/xml/config</configfile>
+
+ <!-- IDMLight -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}</bundle>
+ <configfile finalname="etc/opendaylight/karaf/08-aaa-idmlight-config.xml">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/xml/config</configfile>
+ <configfile finalname="etc/idmtool">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/py/config</configfile>
+
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-core/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.datatype/jackson-datatype-json-org/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/{{VERSION}}</bundle>
+
+ <!-- Federation -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-idpmapping/{{VERSION}}</bundle>
+ <bundle>mvn:org.glassfish/javax.json/{{VERSION}}</bundle>
+
+ <configfile finalname="/etc/org.opendaylight.aaa.authn.cfg">mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}/cfg/config</configfile>
+ <configfile finalname="/etc/org.opendaylight.aaa.tokens.cfg">mvn:org.opendaylight.aaa/aaa-authn-store/{{VERSION}}/cfg/config</configfile>
+ <configfile finalname="/etc/org.opendaylight.aaa.federation.cfg">mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}/cfg/config</configfile>
+ </feature>
+
+ <feature name='odl-aaa-authn' description='OpenDaylight :: AAA :: Authentication - NO CLUSTER'
+ version='${project.version}'>
+ <feature version='${project.version}'>odl-aaa-api</feature>
+
+ <!-- MD-SAL -->
+ <feature version='${yangtools.version}'>odl-yangtools-common</feature>
+ <feature version='${mdsal.version}'>odl-mdsal-binding-base</feature>
+ <feature version='${controller.mdsal.version}'>odl-mdsal-broker</feature>
+ <feature version='${config.version}'>odl-config-core</feature>
+
+ <!-- REST -->
+ <feature>war</feature>
+ <bundle>mvn:com.sun.jersey/jersey-servlet/{{VERSION}}</bundle>
+ <bundle>mvn:com.sun.jersey/jersey-core/{{VERSION}}</bundle>
+ <bundle>mvn:com.sun.jersey/jersey-server/{{VERSION}}</bundle>
+ <bundle>mvn:com.sun.jersey/jersey-client/${jersey.version}</bundle>
+
+ <!-- OSGi -->
+ <bundle>mvn:org.apache.felix/org.apache.felix.dependencymanager/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.felix/org.apache.felix.metatype/{{VERSION}}</bundle>
+
+ <!-- EhCache -->
+ <bundle>mvn:net.sf.ehcache/ehcache/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/{{VERSION}}</bundle>
+
+ <!-- OAuth -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-shiro-act/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.shiro/shiro-core/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.shiro/shiro-web/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.authzserver/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.resourceserver/{{VERSION}}</bundle>
+ <bundle>mvn:commons-codec/commons-codec/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:org.json/json/{{VERSION}}</bundle>
+
+ <!-- commons-lang -->
+ <bundle>wrap:mvn:org.apache.commons/commons-lang3/{{VERSION}}</bundle>
+
+ <!-- AuthN -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-sts/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-store/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-basic/{{VERSION}}</bundle>
+ <bundle>mvn:com.google.guava/guava/{{VERSION}}</bundle>
+
+ <!--H2 Store -->
+ <bundle>mvn:org.osgi/org.osgi.enterprise/4.2.0</bundle>
+ <bundle>wrap:mvn:com.h2database/h2/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-h2-store/{{VERSION}}</bundle>
+ <configfile finalname="etc/opendaylight/karaf/08-aaa-h2-store-config.xml">mvn:org.opendaylight.aaa/aaa-h2-store/{{VERSION}}/xml/config</configfile>
+
+ <!-- IDMLight -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}</bundle>
+ <configfile finalname="etc/opendaylight/karaf/08-aaa-idmlight-config.xml">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/xml/config</configfile>
+ <configfile finalname="etc/idmtool">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/py/config</configfile>
+
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-core/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.datatype/jackson-datatype-json-org/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/{{VERSION}}</bundle>
+
+ <!-- Federation -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-idpmapping/{{VERSION}}</bundle>
+ <bundle>mvn:org.glassfish/javax.json/{{VERSION}}</bundle>
+
+ <configfile finalname="/etc/org.opendaylight.aaa.authn.cfg">mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}/cfg/config</configfile>
+ <configfile finalname="/etc/org.opendaylight.aaa.tokens.cfg">mvn:org.opendaylight.aaa/aaa-authn-store/{{VERSION}}/cfg/config</configfile>
+ <configfile finalname="/etc/org.opendaylight.aaa.federation.cfg">mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}/cfg/config</configfile>
+ </feature>
+
+ <feature name='odl-aaa-authn-mdsal-cluster' description='OpenDaylight :: AAA :: Authentication :: MD-SAL'
+ version='${project.version}'>
+
+ <!-- MD-SAL -->
+ <feature version='${yangtools.version}'>odl-yangtools-common</feature>
+ <feature version='${mdsal.version}'>odl-mdsal-binding-base</feature>
+ <feature version='${controller.mdsal.version}'>odl-mdsal-broker</feature>
+ <feature version='${config.version}'>odl-config-core</feature>
+
+
+ <!-- OSGi -->
+ <bundle>mvn:org.apache.felix/org.apache.felix.dependencymanager/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.felix/org.apache.felix.metatype/{{VERSION}}</bundle>
+
+ <!-- OAuth -->
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.authzserver/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.resourceserver/{{VERSION}}</bundle>
+ <bundle>mvn:commons-codec/commons-codec/1.8</bundle>
+ <bundle>wrap:mvn:org.json/json/{{VERSION}}</bundle>
+
+ <!-- AuthN -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-shiro-act/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.shiro/shiro-core/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.shiro/shiro-web/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-api/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-sts/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-mdsal-api/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-mdsal-store-impl/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-basic/{{VERSION}}</bundle>
+ <bundle>mvn:com.google.guava/guava/{{VERSION}}</bundle>
+
+ <!-- IDMLight -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}</bundle>
+ <configfile finalname="etc/opendaylight/karaf/08-aaa-idmlight-config.xml">mvn:org.opendaylight.aaa/aaa-idmlight/{{VERSION}}/xml/config</configfile>
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-core/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.datatype/jackson-datatype-json-org/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/{{VERSION}}</bundle>
+ <bundle>mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:com.h2database/h2/{{VERSION}}</bundle>
+
+ <!-- Federation -->
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-idpmapping/{{VERSION}}</bundle>
+ <bundle>mvn:org.glassfish/javax.json/1.0.4</bundle>
+
+ <!-- REST -->
+ <feature>war</feature>
+ <bundle>mvn:com.sun.jersey/jersey-servlet/{{VERSION}}</bundle>
+ <bundle>mvn:com.sun.jersey/jersey-core/{{VERSION}}</bundle>
+ <bundle>mvn:com.sun.jersey/jersey-server/{{VERSION}}</bundle>
+
+ <configfile finalname="etc/opendaylight/karaf/08-authn-config.xml">mvn:org.opendaylight.aaa/aaa-authn-mdsal-config/{{VERSION}}/xml/config</configfile>
+ <configfile finalname="/etc/org.opendaylight.aaa.authn.cfg">mvn:org.opendaylight.aaa/aaa-authn/{{VERSION}}/cfg/config</configfile>
+ <configfile finalname="/etc/org.opendaylight.aaa.federation.cfg">mvn:org.opendaylight.aaa/aaa-authn-federation/{{VERSION}}/cfg/config</configfile>
+
+ </feature>
+
+ <feature name='odl-aaa-keystone-plugin' description='OpenDaylight :: AAA :: Keystone Plugin - NO CLUSTER'
+ version='${project.version}'>
+ <feature version='${project.version}'>odl-aaa-authn</feature>
+ <bundle>mvn:org.apache.httpcomponents/httpclient-osgi/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.httpcomponents/httpcore-osgi/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-keystone/{{VERSION}}</bundle>
+ </feature>
+
+ <feature name='odl-aaa-sssd-plugin' description='OpenDaylight :: AAA :: SSSD Federation Plugin'
+ version='${project.version}'>
+ <feature version='${project.version}'>odl-aaa-authn</feature>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-sssd/{{VERSION}}</bundle>
+ </feature>
+
+ <feature name='odl-aaa-authn-sssd-no-cluster' description='OpenDaylight :: AAA :: SSSD Federation - NO CLUSTER'
+ version='${project.version}'>
+ <feature version='${project.version}'>odl-aaa-authn-no-cluster</feature>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authn-sssd/{{VERSION}}</bundle>
+ </feature>
+</features>
diff --git a/odl-aaa-moon/features/authz/pom.xml b/odl-aaa-moon/features/authz/pom.xml
new file mode 100644
index 00000000..41808378
--- /dev/null
+++ b/odl-aaa-moon/features/authz/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>features-parent</artifactId>
+ <version>1.6.1-Beryllium-SR1</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa-authz</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>jar</packaging>
+
+ <properties>
+ <config.version>0.4.1-Beryllium-SR1</config.version>
+ <mdsal.version>2.0.1-Beryllium-SR1</mdsal.version>
+ <controller.mdsal.version>1.3.1-Beryllium-SR1</controller.mdsal.version>
+ <yangtools.version>0.8.1-Beryllium-SR1</yangtools.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- This project -->
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>${project.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa-api</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <!-- odl-aaa-authz -->
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>features-yangtools</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>features-mdsal</artifactId>
+ <classifier>features</classifier>
+ <version>${mdsal.version}</version>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>features-config</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>features-mdsal</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>authz-restconf-config</artifactId>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authz-model</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authz-service</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>authz-service-config</artifactId>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </dependency>
+ </dependencies>
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</developerConnection>
+ <tag>HEAD</tag>
+ <url>https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary</url>
+ </scm>
+</project>
diff --git a/odl-aaa-moon/features/authz/src/main/features/features.xml b/odl-aaa-moon/features/authz/src/main/features/features.xml
new file mode 100644
index 00000000..c5239045
--- /dev/null
+++ b/odl-aaa-moon/features/authz/src/main/features/features.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!-- 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 -->
+<features name="odl-aaa-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
+ <repository>mvn:org.opendaylight.yangtools/features-yangtools/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.controller/features-config/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.mdsal/features-mdsal/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.controller/features-mdsal/{{VERSION}}/xml/features</repository>
+ <repository>mvn:org.opendaylight.aaa/features-aaa-api/{{VERSION}}/xml/features</repository>
+
+ <feature name='odl-aaa-authz' description='OpenDaylight :: AAA :: Authorization'
+ version='${project.version}'>
+ <feature version='${project.version}'>odl-aaa-api</feature>
+ <feature version='${yangtools.version}'>odl-yangtools-common</feature>
+ <feature version='${mdsal.version}'>odl-mdsal-binding-base</feature>
+ <feature version='${controller.mdsal.version}'>odl-mdsal-broker</feature>
+ <feature version='${config.version}'>odl-config-core</feature>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authz-model/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-authz-service/{{VERSION}}</bundle>
+ <configfile
+ finalname="/etc/opendaylight/karaf/08-authz-config.xml">mvn:org.opendaylight.aaa/authz-service-config/{{VERSION}}/xml/config</configfile>
+ <configfile
+ finalname="/etc/opendaylight/karaf/09-rest-connector.xml">mvn:org.opendaylight.aaa/authz-restconf-config/{{VERSION}}/xml/config</configfile>
+ </feature>
+
+</features>
diff --git a/odl-aaa-moon/features/pom.xml b/odl-aaa-moon/features/pom.xml
new file mode 100644
index 00000000..261f73c4
--- /dev/null
+++ b/odl-aaa-moon/features/pom.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aggregator</artifactId>
+ <packaging>pom</packaging>
+ <modules>
+ <module>shiro</module>
+ <module>api</module>
+ <module>authn</module>
+ <module>authz</module>
+ </modules>
+</project>
diff --git a/odl-aaa-moon/features/shiro/pom.xml b/odl-aaa-moon/features/shiro/pom.xml
new file mode 100644
index 00000000..50a9971e
--- /dev/null
+++ b/odl-aaa-moon/features/shiro/pom.xml
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (c) 2015 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 -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>features-parent</artifactId>
+ <version>1.6.1-Beryllium-SR1</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa-shiro</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>jar</packaging>
+
+ <properties>
+ <javax.annotation.api.version>1.2</javax.annotation.api.version>
+ <servicemix.version>1.8.3_2</servicemix.version>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <!-- This project -->
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>${project.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>features-aaa</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-shiro-act</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-shiro</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <type>cfg</type>
+ <classifier>configuration</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-shiro</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-sts</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.metatype</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-shiro</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-sts</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <version>${javax.annotation.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.metatype</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.servicemix.bundles</groupId>
+ <artifactId>org.apache.servicemix.bundles.commons-beanutils</artifactId>
+ <version>${servicemix.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ </dependencies>
+
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</developerConnection>
+ <tag>HEAD</tag>
+ <url>https://git.opendaylight.org/gerrit/gitweb?p=aaa.git;a=summary</url>
+ </scm>
+</project>
diff --git a/odl-aaa-moon/features/shiro/src/main/features/features.xml b/odl-aaa-moon/features/shiro/src/main/features/features.xml
new file mode 100644
index 00000000..c6073a2a
--- /dev/null
+++ b/odl-aaa-moon/features/shiro/src/main/features/features.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (c) 2015 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 -->
+<features name="odl-aaa-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
+
+ <repository>mvn:org.opendaylight.aaa/features-aaa/{{VERSION}}/xml/features</repository>
+
+ <!-- odl-aaa-shiro feature which combines all aspects of AAA into one feature -->
+ <feature name='odl-aaa-shiro' description='OpenDaylight :: AAA :: Shiro'
+ version='${project.version}'>
+
+ <!-- OSGI -->
+ <bundle>mvn:org.apache.felix/org.apache.felix.dependencymanager/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.felix/org.apache.felix.metatype/{{VERSION}}</bundle>
+
+ <!-- Existing AAA infrastructure -->
+ <feature version='${project.version}'>odl-aaa-authn</feature>
+
+ <bundle>mvn:org.apache.shiro/shiro-web/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.shiro/shiro-core/{{VERSION}}</bundle>
+
+ <bundle>mvn:com.google.guava/guava/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:javax.annotation/javax.annotation-api/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:com.google.code.findbugs/jsr305/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:commons-codec/commons-codec/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.resourceserver/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.authzserver/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:org.apache.oltu.oauth2/org.apache.oltu.oauth2.common/{{VERSION}}</bundle>
+ <bundle>wrap:mvn:org.json/json/{{VERSION}}</bundle>
+ <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-beanutils/{{VERSION}}</bundle>
+ <bundle>mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}}</bundle>
+
+ <!-- AAA configuration file -->
+ <configfile finalname="/etc/shiro.ini">mvn:org.opendaylight.aaa/aaa-shiro/{{VERSION}}/cfg/configuration</configfile>
+ </feature>
+
+</features>
diff --git a/odl-aaa-moon/parent/pom.xml b/odl-aaa-moon/parent/pom.xml
new file mode 100644
index 00000000..37bf3ad2
--- /dev/null
+++ b/odl-aaa-moon/parent/pom.xml
@@ -0,0 +1,278 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>odlparent</artifactId>
+ <version>1.6.1-Beryllium-SR1</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>pom</packaging>
+ <prerequisites>
+ <maven>3.0.4</maven>
+ </prerequisites>
+
+ <properties>
+ <!-- Karaf -->
+ <karaf.branding.version>1.2.1-Beryllium-SR1</karaf.branding.version>
+ <karaf.resources.version>1.6.1-Beryllium-SR1</karaf.resources.version>
+
+ <!-- OSGi -->
+ <osgi.metatype.version>1.0.10</osgi.metatype.version>
+
+ <!-- Local project version, needed for import -->
+ <aaa.version>${project.version}</aaa.version>
+ <parent-path>${basedir}</parent-path>
+
+ <!-- AuthZ -->
+ <yangtools.version>0.8.1-Beryllium-SR1</yangtools.version>
+ <jmxGeneratorPath>src/main/yang-gen-config</jmxGeneratorPath>
+ <salGeneratorPath>src/main/yang-gen-sal</salGeneratorPath>
+ <mdsal.version>2.0.1-Beryllium-SR1</mdsal.version>
+ <mdsal.model.version>0.8.1-Beryllium-SR1</mdsal.model.version>
+ <controller.mdsal.version>1.3.1-Beryllium-SR1</controller.mdsal.version>
+ <restconf.version>1.3.1-Beryllium-SR1</restconf.version>
+ <config.version>0.4.1-Beryllium-SR1</config.version>
+ <config.authz.service.configfile>08-authz-config.xml</config.authz.service.configfile>
+ <config.restconf.configfile>09-rest-connector.xml</config.restconf.configfile>
+ <config.configfile.directory>etc/opendaylight/karaf</config.configfile.directory>
+
+ <!-- AuthN -->
+ <glassfish.json.version>1.0.4</glassfish.json.version>
+ <ehcache.version>2.8.3</ehcache.version>
+ <jta.version>1.1.1</jta.version>
+ <oltu.version>1.0.0</oltu.version>
+
+ <config.authn.store.configfile>08-authn-config.xml</config.authn.store.configfile>
+
+ <!-- IdmLight -->
+ <h2.version>1.4.185</h2.version>
+
+ <!-- Keystone plugin -->
+ <httpclient.version>4.4</httpclient.version>
+
+ <!-- Test -->
+ <javax.inject.version>1</javax.inject.version>
+ <servlet.tester.version>7.0.0.M2</servlet.tester.version>
+ <features.test.version>1.6.1-Beryllium-SR1</features.test.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- ODL -->
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-artifacts</artifactId>
+ <version>${aaa.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yangtools-artifacts</artifactId>
+ <version>${yangtools.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>mdsal-artifacts</artifactId>
+ <version>${mdsal.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>mdsal-model-artifacts</artifactId>
+ <version>${mdsal.model.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-artifacts</artifactId>
+ <version>${controller.mdsal.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-artifacts</artifactId>
+ <version>${config.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+
+ <!-- Third-party -->
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.json</artifactId>
+ <version>${glassfish.json.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.metatype</artifactId>
+ <version>${osgi.metatype.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ <version>${ehcache.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jta_1.1_spec</artifactId>
+ <version>${jta.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.common</artifactId>
+ <version>${oltu.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
+ <version>${oltu.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.oltu.oauth2</groupId>
+ <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
+ <version>${oltu.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <version>${h2.version}</version>
+ </dependency>
+
+ <!-- Test stuff -->
+ <dependency>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>features-test</artifactId>
+ <version>${features.test.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>${javax.inject.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet-tester</artifactId>
+ <version>${servlet.tester.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>org.opendaylight.aaa.*</include>
+ </includes>
+ </configuration>
+ <executions>
+ <execution>
+ <id>pre-test</id>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>post-test</id>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ <phase>test</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <configuration>
+ <!-- checkstyle is evil -->
+ <skip>false</skip>
+ <failOnViolation>true</failOnViolation>
+ <configLocation>checkstyle-logging.xml</configLocation>
+ <consoleOutput>true</consoleOutput>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ <sourceDirectory>${project.basedir}</sourceDirectory>
+ <includes>**\/*.java,**\/*.xml,**\/*.ini,**\/*.sh,**\/*.bat,**\/*.yang</includes>
+ <excludes>**\/target\/,**\/bin\/,**\/target-ide\/,**\/src/main/yang-gen-config\/,**\/src/main/yang-gen-sal\/</excludes>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <phase>process-sources</phase>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>checkstyle-logging</artifactId>
+ <version>${yangtools.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${jmxGeneratorPath}</source>
+ <source>${salGeneratorPath}</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <url>https://wiki.opendaylight.org/view/AAA:Main</url>
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</developerConnection>
+ <tag>HEAD</tag>
+ </scm>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>${findbugs.maven.plugin.version}</version>
+ <configuration>
+ <effort>Max</effort>
+ <threshold>Low</threshold>
+ <goal>site</goal>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>jdepend-maven-plugin</artifactId>
+ <version>${jdepend.maven.plugin.version}</version>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
diff --git a/odl-aaa-moon/pom.xml b/odl-aaa-moon/pom.xml
new file mode 100644
index 00000000..3d6591e2
--- /dev/null
+++ b/odl-aaa-moon/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>parent</relativePath>
+ </parent>
+
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa.project</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <packaging>pom</packaging>
+ <name>aaa</name> <!-- Used by Sonar to set project name -->
+ <prerequisites>
+ <maven>3.0</maven>
+ </prerequisites>
+
+ <modules>
+ <module>aaa-authn-api</module>
+ <module>aaa-authn</module>
+ <module>aaa-idp-mapping</module>
+ <module>aaa-authn-sts</module>
+ <module>aaa-authn-store</module>
+ <module>aaa-authn-federation</module>
+ <module>aaa-authn-sssd</module>
+ <module>aaa-authn-keystone</module>
+ <module>aaa-authn-basic</module>
+ <module>aaa-idmlight</module>
+ <module>aaa-authn-mdsal-store</module>
+ <module>aaa-authz</module>
+ <module>aaa-credential-store-api</module>
+ <module>artifacts</module>
+ <module>features</module>
+ <module>distribution-karaf</module>
+ <module>parent</module>
+ <module>aaa-shiro</module>
+ <module>aaa-shiro-act</module>
+ <module>aaa-h2-store</module>
+ </modules>
+
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/aaa.git</developerConnection>
+ <tag>HEAD</tag>
+ <url>https://wiki.opendaylight.org/view/AAA:Main</url>
+ </scm>
+
+</project>